Fuel Docs Hub
This mdbook contains documentation from multiple repositories in the Fuel ecosystem.
Included Repositories
- Sway Language
- Sway Libraries
- Sway Standards
- Sway by Example
- Migrations and Disclosures
- Verified Addresses
- Fuel Token Overview
- Fuel Book
- Integration Docs
- Node Operator
- Fuels-rs (Rust SDK)
- Fuels-ts (TypeScript SDK)
- Specs
- Guides
- Introduction
- Contributing
Introduction
To get started with Forc and Sway smart contract development, install the Fuel toolchain and Fuel full node and set up your first project.
Getting Started
Installing the Fuel toolchain
Please visit the Fuel Installation Guide to install the Fuel toolchain binaries and prerequisites.
Sway Quickstart
Check out the Developer Quickstart Guide for a step-by-step guide on building a fullstack dapp on Fuel. The guide will walk you through writing a smart contract, setting up a wallet, and building a frontend to interact with your contract.
The Fuel Toolchain
The Fuel toolchain consists of several components.
Forc (forc)
The "Fuel Orchestrator" Forc is our equivalent of Rust's Cargo. It is the primary entry point for creating, building, testing, and deploying Sway projects.
Sway Language Server (forc-lsp)
The Sway Language Server forc-lsp is provided to expose features to IDEs. Installation instructions.
Sway Formatter (forc-fmt)
A canonical formatter is provided with forc-fmt. Installation instructions. It can be run manually with
forc fmt
The Visual Studio Code plugin will
automatically format Sway files with forc-fmt on save, though you might have to explicitly set the Sway plugin as the
default formatter, like this:
"[sway]": {
"editor.defaultFormatter": "FuelLabs.sway-vscode-plugin"
}
Fuel Core (fuel-core)
An implementation of the Fuel protocol, Fuel Core, is provided together with the Sway toolchain to form the Fuel toolchain. The Rust SDK will automatically start and stop an instance of the node during tests, so there is no need to manually run a node unless using Forc directly without the SDK.
A Forc Project
To initialize a new project with Forc, use forc new:
forc new my-fuel-project
Here is the project that Forc has initialized:
$ cd my-fuel-project
$ tree .
├── Forc.toml
└── src
└── main.sw
Forc.toml is the manifest file (similar to Cargo.toml for Cargo or package.json for Node), and defines project metadata such as the project name and dependencies.
For additional information on dependency management, see: here.
[project]
authors = ["User"]
entry = "main.sw"
license = "Apache-2.0"
name = "my-fuel-project"
[dependencies]
Here are the contents of the only Sway file in the project, and the main entry point, src/main.sw:
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}
The project is a contract, one of four different project types. For additional information on different project types, see here.
We now compile our project with forc build, passing the flag --asm final to view the generated assembly:
$ forc build --asm final
...
.program:
ji i4
noop
DATA_SECTION_OFFSET[0..32]
DATA_SECTION_OFFSET[32..64]
lw $ds $is 1
add $$ds $$ds $is
lw $r0 $fp i73 ; load input function selector
lw $r1 data_0 ; load fn selector for comparison
eq $r2 $r0 $r1 ; function selector comparison
jnzi $r2 i12 ; jump to selected function
movi $$tmp i123 ; special code for mismatched selector
rvrt $$tmp ; revert if no selectors matched
ret $one
.data:
data_0 .word 559005003
Compiled contract "my-fuel-project".
Bytecode size is 60 bytes.
Standard Library
Similar to Rust, Sway comes with its own standard library.
The Sway Standard Library is the foundation of portable Sway software, a set of minimal shared abstractions for the broader Sway ecosystem. It offers core types, like Result<T, E> and Option<T>, library-defined operations on language primitives, native asset management, blockchain contextual operations, access control, storage management, and support for types from other VMs, among many other things.
The entire Sway standard library is a Forc project called std, and is available directly here. Navigate to the appropriate tagged release if the latest master is not compatible. You can find the latest std documentation here.
Using the Standard Library
The standard library is made implicitly available to all Forc projects created using forc new. In other words, it is not required to manually specify std as an explicit dependency. Forc will automatically use the version of std that matches its version.
Importing items from the standard library can be done using the use keyword, just as importing items from any Sway project. For example:
use std::storage::storage_vec::*;
This imports the StorageVec type into the current namespace.
Standard Library Prelude
Sway comes with a variety of things in its standard library. However, if you had to manually import every single thing that you used, it would be very verbose. But importing a lot of things that a program never uses isn't good either. A balance needs to be struck.
The prelude is the list of things that Sway automatically imports into every Sway program. It's kept as small as possible, and is focused on things which are used in almost every single Sway program.
The current version of the prelude lives in std::prelude, and re-exports the following:
std::address::Address, a wrapper around theb256type representing a wallet address.std::contract_id::ContractId, a wrapper around theb256type representing the ID of a contract.std::identity::Identity, an enum with two possible variants:Address: AddressandContractId: ContractId.std::vec::Vec, a growable, heap-allocated vector.std::storage::storage_key::*, contains the API for accessing astd::storage::StorageKeywhich describes a location in storage.std::storage::storage_map::*, a key-value mapping in contract storage.std::option::Option, an enum which expresses the presence or absence of a value.std::result::Result, an enum for functions that may succeed or fail.std::assert::assert, a function that reverts the VM if the condition provided to it isfalse.std::assert::assert_eq, a function that reverts the VM and logs its two inputsv1andv2if the conditionv1==v2isfalse.std::assert::assert_ne, a function that reverts the VM and logs its two inputsv1andv2if the conditionv1!=v2isfalse.std::revert::require, a function that reverts the VM and logs a given value if the condition provided to it isfalse.std::revert::revert, a function that reverts the VM.std::logging::log, a function that logs arbitrary stack types.std::auth::msg_sender, a function that gets theIdentityfrom which a call was made.std::primitives::*, methods on primitive types.std::primitive_conversions::*, methods for converting between primitive types.std::raw_ptr::*, functions for working with raw pointers.std::raw_slice::*, functions for working with raw slices.std::ops::*, mathematical operations such as addition, subtraction, multiplication, and division.std::str::*, methods for working with strings.std::codec::*, automatic serialization and deserialization of types.
Sway Standards
Just like many other smart contract languages, usage standards have been developed to enable cross compatibility between smart contracts.
For more information on using a Sway Standard, please refer to the Sway-Standards Repository.
Standards
Native Asset Standards
- SRC-20; Native Asset Standard defines the implementation of a standard API for Native Assets using the Sway Language.
- SRC-3; Mint and Burn is used to enable mint and burn functionality for Native Assets.
- SRC-7; Arbitrary Asset Metadata Standard is used to store metadata for Native Assets, usually as NFTs.
- SRC-9; Metadata Keys Standard is used to store standardized metadata keys for Native Assets in combination with the SRC-7 standard.
- SRC-6; Vault Standard defines the implementation of a standard API for asset vaults developed in Sway.
Predicate Standards
- SRC-13; Soulbound Address Standard defines a specific
Addressas a Soulbound Address for Soulbound Assets to become non-transferable.
Access Control Standards
- SRC-5; Ownership Standard is used to restrict function calls to admin users in contracts.
Contract Standards
- SRC-12; Contract Factory defines the implementation of a standard API for contract factories.
Bridge Standards
- SRC-8; Bridged Asset defines the metadata required for an asset bridged to the Fuel Network.
- SRC-10; Native Bridge Standard defines the standard API for the Native Bridge between the Fuel Chain and the canonical base chain.
Documentation Standards
- SRC-2; Inline Documentation defines how to document your Sway files.
Standards Support
Libraries have also been developed to support Sway Standards. These can be in Sway-Libs.
Example
Some basic example contracts to see how Sway and Forc work.
Additional examples can be found in the Sway Applications repository.
Counter
The following is a simple example of a contract which implements a counter. Both the initialize_counter() and increment_counter() ABI methods return the currently set value.
forc template --template-name counter my_counter_project
contract;
abi TestContract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64;
#[storage(read, write)]
fn increment_counter(amount: u64) -> u64;
}
storage {
counter: u64 = 0,
}
impl TestContract for Contract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64 {
storage.counter.write(value);
value
}
#[storage(read, write)]
fn increment_counter(amount: u64) -> u64 {
let incremented = storage.counter.read() + amount;
storage.counter.write(incremented);
incremented
}
}
Build and deploy
The following commands can be used to build and deploy the contract. For a detailed tutorial, refer to Building and Deploying.
# Build the contract
forc build
# Deploy the contract
forc deploy --testnet
FizzBuzz
This example is not the traditional FizzBuzz; instead it is the smart contract version! A script can call the fizzbuzz ABI method of this contract with some u64 value and receive back the result as an enum.
The format for custom structs and enums such as FizzBuzzResult will be automatically included in the ABI JSON so that off-chain code can handle the encoded form of the returned data.
contract;
enum FizzBuzzResult {
Fizz: (),
Buzz: (),
FizzBuzz: (),
Other: u64,
}
abi FizzBuzz {
fn fizzbuzz(input: u64) -> FizzBuzzResult;
}
impl FizzBuzz for Contract {
fn fizzbuzz(input: u64) -> FizzBuzzResult {
if input % 15 == 0 {
FizzBuzzResult::FizzBuzz
} else if input % 3 == 0 {
FizzBuzzResult::Fizz
} else if input % 5 == 0 {
FizzBuzzResult::Buzz
} else {
FizzBuzzResult::Other(input)
}
}
}
Wallet Smart Contract
The ABI declaration is a separate project from your ABI implementation. The project structure for the code should be organized as follows with the wallet_abi treated as an external library:
.
├── wallet_abi
│ ├── Forc.toml
│ └── src
│ └── main.sw
└── wallet_smart_contract
├── Forc.toml
└── src
└── main.sw
It's also important to specify the source of the dependency within the project's Forc.toml file when using external libraries. Inside the wallet_smart_contract project, it requires a declaration like this:
[dependencies]
wallet_abi = { path = "../wallet_abi/" }
ABI Declaration
// ANCHOR: abi_library
library;
// ANCHOR: abi
abi Wallet {
// ANCHOR: receive_funds
#[storage(read, write), payable]
fn receive_funds();
// ANCHOR_END: receive_funds
// ANCHOR: send_funds
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);
// ANCHOR_END: send_funds
}
// ANCHOR: abi
// ANCHOR_END: abi_library
ABI Implementation
// ANCHOR: full_wallet
contract;
use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount};
// ANCHOR: abi_import
use wallet_abi::Wallet;
// ANCHOR_END: abi_import
const OWNER_ADDRESS = Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);
storage {
balance: u64 = 0,
}
// ANCHOR: abi_impl
impl Wallet for Contract {
#[storage(read, write), payable]
fn receive_funds() {
if msg_asset_id() == AssetId::base() {
// If we received the base asset then keep track of the balance.
// Otherwise, we're receiving other native assets and don't care
// about our balance of coins.
storage.balance.write(storage.balance.read() + msg_amount());
}
}
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address) {
let sender = msg_sender().unwrap();
match sender {
Identity::Address(addr) => assert(addr == OWNER_ADDRESS),
_ => revert(0),
};
let current_balance = storage.balance.read();
assert(current_balance >= amount_to_send);
storage.balance.write(current_balance - amount_to_send);
// Note: `transfer()` is not a call and thus not an
// interaction. Regardless, this code conforms to
// checks-effects-interactions to avoid re-entrancy.
transfer(
Identity::Address(recipient_address),
AssetId::base(),
amount_to_send,
);
}
}
// ANCHOR_END: abi_impl
// ANCHOR_END: full_wallet
Liquidity Pool Example
All contracts in Fuel can mint and burn their own native asset. Contracts can also receive and transfer any native asset including their own. Internal balances of all native assets pushed through calls or minted by the contract are tracked by the FuelVM and can be queried at any point using the balance_of function from the std library. Therefore, there is no need for any manual accounting of the contract's balances using persistent storage.
The std library provides handy methods for accessing Fuel's native asset operations.
In this example, we show a basic liquidity pool contract minting its own native asset LP asset.
contract;
use std::{
asset::{
mint_to,
transfer,
},
call_frames::msg_asset_id,
constants::DEFAULT_SUB_ID,
context::msg_amount,
hash::*,
};
abi LiquidityPool {
fn deposit(recipient: Address);
fn withdraw(recipient: Address);
}
const BASE_ASSET: AssetId = AssetId::from(0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c);
impl LiquidityPool for Contract {
fn deposit(recipient: Address) {
assert(msg_asset_id() == BASE_ASSET);
assert(msg_amount() > 0);
// Mint two times the amount.
let amount_to_mint = msg_amount() * 2;
// Mint some LP assets based upon the amount of the base asset.
mint_to(Identity::Address(recipient), DEFAULT_SUB_ID, amount_to_mint);
}
fn withdraw(recipient: Address) {
let asset_id = AssetId::default();
assert(msg_asset_id() == asset_id);
assert(msg_amount() > 0);
// Amount to withdraw.
let amount_to_transfer = msg_amount() / 2;
// Transfer base asset to recipient.
transfer(Identity::Address(recipient), BASE_ASSET, amount_to_transfer);
}
}
Sway Applications
The Sway-Applications Repository contains end-to-end example applications that are written in Sway in order to demonstrate what can be built.
Asset Management
- Airdrop is an asset distribution program where users are able to claim assets given a valid merkle proof.
- Escrow is a third party that keeps an asset on behalf of multiple parties.
- Non-Fungible Native Asset (NFT) is an asset contract which provides unique collectibles, identified and differentiated by IDs, where assets contain metadata giving them distinctive characteristics.
- Fractional Non-Fungible Token (F-NFT) is a token contract which issues shares or partial ownership upon locking an NFT into a vault.
- Timelock is a contract which restricts the execution of a transaction to a specified time range.
- Native Asset is a basic asset contract that enables the use of Native Assets on Fuel using existing standards and libraries.
Decentralized Finance
- English Auction is an auction where users bid up the price of an asset until the bidding period has ended or a reserve has been met.
- Fundraiser is a program allowing users to pledge towards a goal.
- OTC Swap Predicate is a predicate that can be used to propose and execute an atomic swap between two parties without requiring any on-chain state.
Governance
- Decentralized Autonomous Organization (DAO) is an organization where users get to vote on governance proposals using governance assets.
- Multi-Signature Wallet is a wallet that requires multiple signatures to execute a transaction.
Games
- TicTacToe is a game where two players compete to align three markers in a row.
Other
- Counter-Script is a script that calls a contract to increment a counter.
- Name-Registry allows users to perform transactions with human readable names instead of addresses.
- Oracle is a smart contract that provides off-chain data to on-chain applications.
Sway Program Types
A Sway program itself has a type: it is either a contract, a predicate, a script, or a library. The first three of these things are all deployable to the blockchain. A library is simply a project designed for code reuse and is never directly deployed to the chain.
Every Sway file must begin with a declaration of what type of program it is. A project can have many libraries within it, but only one contract, script, or predicate. Scripts and predicates require main functions to serve as entry points, while contracts instead publish an ABI. This chapter will go into detail about all of these various types of programs and what purposes they serve.
Contracts are used primarily for protocols or systems that operate within a fixed set of rules. A good example would be a staking contract or a decentralized exchange (also called a DEX).
Scripts are used for complex on-chain interactions that won't persist. An example of this may be using a DEX and Lender to create a leveraged position (borrow, swap, re-collateralize) which is a complex transaction that would usually take multiple steps.
Libraries are for code that is reusable and useful for handling common situations. A good example of this would be a library to handle fixed-point math or big number math.
What is a Smart Contract?
A smart contract is no different than a script or predicate in that it is a piece of bytecode that is deployed to the blockchain via a transaction. The main features of a smart contract that differentiate it from scripts or predicates are that it is callable and stateful. Put another way, a smart contract is analogous to a deployed API with some database state.
The interface of a smart contract, also just called a contract, must be defined strictly with an ABI declaration. See this contract for an example.
Syntax of a Smart Contract
As with any Sway program, the program starts with a declaration of what program type it is. A contract must also either define or import an ABI declaration and implement it.
It is considered good practice to define your ABI in a separate library and import it into your contract. This allows callers of your contract to simply import the ABI directly and use it in their scripts to call your contract.
Let's take a look at an ABI declaration in a library:
// ANCHOR: abi_library
library;
// ANCHOR: abi
abi Wallet {
// ANCHOR: receive_funds
#[storage(read, write), payable]
fn receive_funds();
// ANCHOR_END: receive_funds
// ANCHOR: send_funds
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);
// ANCHOR_END: send_funds
}
// ANCHOR: abi
// ANCHOR_END: abi_library
Let's focus on the ABI declaration and inspect it line-by-line.
The ABI Declaration
// ANCHOR: abi_library
library;
// ANCHOR: abi
abi Wallet {
// ANCHOR: receive_funds
#[storage(read, write), payable]
fn receive_funds();
// ANCHOR_END: receive_funds
// ANCHOR: send_funds
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);
// ANCHOR_END: send_funds
}
// ANCHOR: abi
// ANCHOR_END: abi_library
In the first line, abi Wallet {, we declare the name of this Application Binary Interface, or ABI. We are naming this ABI Wallet. To import this ABI into either a script for calling or a contract for implementing, you would use
// ANCHOR: full_wallet
contract;
use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount};
// ANCHOR: abi_import
use wallet_abi::Wallet;
// ANCHOR_END: abi_import
const OWNER_ADDRESS = Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);
storage {
balance: u64 = 0,
}
// ANCHOR: abi_impl
impl Wallet for Contract {
#[storage(read, write), payable]
fn receive_funds() {
if msg_asset_id() == AssetId::base() {
// If we received the base asset then keep track of the balance.
// Otherwise, we're receiving other native assets and don't care
// about our balance of coins.
storage.balance.write(storage.balance.read() + msg_amount());
}
}
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address) {
let sender = msg_sender().unwrap();
match sender {
Identity::Address(addr) => assert(addr == OWNER_ADDRESS),
_ => revert(0),
};
let current_balance = storage.balance.read();
assert(current_balance >= amount_to_send);
storage.balance.write(current_balance - amount_to_send);
// Note: `transfer()` is not a call and thus not an
// interaction. Regardless, this code conforms to
// checks-effects-interactions to avoid re-entrancy.
transfer(
Identity::Address(recipient_address),
AssetId::base(),
amount_to_send,
);
}
}
// ANCHOR_END: abi_impl
// ANCHOR_END: full_wallet
In the second line,
// ANCHOR: abi_library
library;
// ANCHOR: abi
abi Wallet {
// ANCHOR: receive_funds
#[storage(read, write), payable]
fn receive_funds();
// ANCHOR_END: receive_funds
// ANCHOR: send_funds
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);
// ANCHOR_END: send_funds
}
// ANCHOR: abi
// ANCHOR_END: abi_library
we are declaring an ABI method called receive_funds which, when called, should receive funds into this wallet. Note that we are simply defining an interface here, so there is no function body or implementation of the function. We only need to define the interface itself. In this way, ABI declarations are similar to trait declarations. This particular ABI method does not take any parameters.
In the third line,
// ANCHOR: abi_library
library;
// ANCHOR: abi
abi Wallet {
// ANCHOR: receive_funds
#[storage(read, write), payable]
fn receive_funds();
// ANCHOR_END: receive_funds
// ANCHOR: send_funds
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address);
// ANCHOR_END: send_funds
}
// ANCHOR: abi
// ANCHOR_END: abi_library
we are declaring another ABI method, this time called send_funds. It takes two parameters: the amount to send, and the address to send the funds to.
Note: The ABI methods
receive_fundsandsend_fundsalso require the annotation#[storage(read, write)]because their implementations require reading and writing a storage variable that keeps track of the wallet balance, as we will see shortly. Refer to Purity for more information on storage annotations.
Implementing an ABI for a Smart Contract
Now that we've discussed how to define the interface, let's discuss how to use it. We will start by implementing the above ABI for a specific contract.
Implementing an ABI for a contract is accomplished with impl <ABI name> for Contract syntax. The for Contract syntax can only be used to implement an ABI for a contract; implementing methods for a struct should use impl Foo syntax.
// ANCHOR: full_wallet
contract;
use std::{asset::transfer, call_frames::msg_asset_id, context::msg_amount};
// ANCHOR: abi_import
use wallet_abi::Wallet;
// ANCHOR_END: abi_import
const OWNER_ADDRESS = Address::from(0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861);
storage {
balance: u64 = 0,
}
// ANCHOR: abi_impl
impl Wallet for Contract {
#[storage(read, write), payable]
fn receive_funds() {
if msg_asset_id() == AssetId::base() {
// If we received the base asset then keep track of the balance.
// Otherwise, we're receiving other native assets and don't care
// about our balance of coins.
storage.balance.write(storage.balance.read() + msg_amount());
}
}
#[storage(read, write)]
fn send_funds(amount_to_send: u64, recipient_address: Address) {
let sender = msg_sender().unwrap();
match sender {
Identity::Address(addr) => assert(addr == OWNER_ADDRESS),
_ => revert(0),
};
let current_balance = storage.balance.read();
assert(current_balance >= amount_to_send);
storage.balance.write(current_balance - amount_to_send);
// Note: `transfer()` is not a call and thus not an
// interaction. Regardless, this code conforms to
// checks-effects-interactions to avoid re-entrancy.
transfer(
Identity::Address(recipient_address),
AssetId::base(),
amount_to_send,
);
}
}
// ANCHOR_END: abi_impl
// ANCHOR_END: full_wallet
You may notice once again the similarities between traits and ABIs. And, indeed, as a bonus, you can define methods in addition to the interface surface of an ABI, just like a trait. These pre-implemented ABI methods automatically become available as part of the contract interface that implements the corresponding ABI.
Note that the above implementation of the ABI follows the Checks, Effects, Interactions pattern.
The ContractId type
Contracts have an associated ContractId type in Sway. The ContractId type allows for Sway programs to refer to contracts in the Sway language. Please refer to the ContractId section of the book for more information on ContractIds.
Calling a Smart Contract from a Script
Note: In most cases, calling a contract should be done from the Rust SDK or the TypeScript SDK which provide a more ergonomic UI for interacting with a contract. However, there are situations where manually writing a script to call a contract is required.
Now that we have defined our interface and implemented it for our contract, we need to know how to actually call our contract. Let's take a look at a contract call:
script;
use wallet_abi::Wallet;
fn main() {
let contract_address = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;
let caller = abi(Wallet, contract_address);
let amount_to_send = 200;
let recipient_address = Address::from(0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b);
caller
.send_funds {
gas: 10000,
coins: 0,
asset_id: b256::zero(),
}(amount_to_send, recipient_address);
}
The main new concept is the abi cast: abi(AbiName, contract_address). This returns a ContractCaller type which can be used to call contracts. The methods of the ABI become the methods available on this contract caller: send_funds and receive_funds. We then directly call the contract ABI method as if it was just a regular method. You also have the option of specifying the following special parameters inside curly braces right before the main list of parameters:
gas: au64that represents the gas being forwarded to the contract when it is called.coins: au64that represents how many coins are being forwarded with this call.asset_id: ab256that represents the ID of the asset type of the coins being forwarded.
Each special parameter is optional and assumes a default value when skipped:
- The default value for
gasis the context gas (i.e. the content of the special register$cgas). Refer to the FuelVM specifications for more information about context gas. - The default value for
coinsis 0. - The default value for
asset_idisb256::zero().
Libraries
Libraries in Sway are files used to define new common behavior.
The most prominent example of this is the Sway Standard Library that is made implicitly available to all Forc projects created using forc new.
Writing Libraries
Libraries are defined using the library keyword at the beginning of a file, followed by a name so that they can be imported.
library;
// library code
A good reference library to use when learning library design is the Sway Standard Library. For example, the standard library offers an implementation of enum Option<T> which is a generic type that represents either the existence of a value using the variant Some(..) or a value's absence using the variant None. The Sway file implementing Option<T> has the following structure:
- The
librarykeyword:
library;
- A
usestatement that importsrevertfrom another library inside the standard library:
use ::revert::revert;
- The
enumdefinition which starts with the keywordpubto indicate that thisOption<T>is publicly available outside theoptionlibrary:
pub enum Option<T> {
// variants
}
- An
implblock that implements some methods forOption<T>:
impl<T> Option<T> {
fn is_some(self) -> bool {
// body of is_some
}
// other methods
}
Now that the library option is fully written, and because Option<T> is defined with the pub keyword, we are now able to import Option<T> using use std::option::Option; from any Sway project and have access to all of its variants and methods. That being said, Option is automatically available in the standard library prelude so you never actually have to import it manually.
Libraries are composed of just a Forc.toml file and a src directory, unlike contracts which usually contain a tests directory and a Cargo.toml file as well. An example of a library's Forc.toml:
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "lib.sw"
license = "Apache-2.0"
name = "my_library"
[dependencies]
which denotes the authors, an entry file, the name by which it can be imported, and any dependencies.
For large libraries, it is recommended to have a lib.sw entry point re-export all other sub-libraries.
The mod keyword registers a submodule, making its items (such as functions and structs) accessible from the parent library.
If used at the top level it will refer to a file in the src folder and in other cases in a folder named after the library in which it is defined.
For example, the lib.sw of the standard library looks like:
library;
mod block;
mod storage;
mod constants;
mod vm;
// .. Other deps
with other libraries contained in the src folder, like the vm library (inside of src/vm.sw):
library;
mod evm;
// ...
and it's own sub-library evm located in src/vm/evm.sw:
library;
// ...
Using Libraries
There are two types of Sway libraries, based on their location and how they can be imported.
Internal Libraries
Internal libraries are located within the project's src directory alongside
main.sw or in the appropriate folders as shown below:
$ tree
.
├── Cargo.toml
├── Forc.toml
└── src
├── internal_lib.sw
├── main.sw
└── internal_lib
└── nested_lib.sw
As internal_lib is an internal library, it can be imported into main.sw as follows:
- Use the
modkeyword followed by the library name to make the internal library a dependency - Use the
usekeyword with a::separating the name of the library and the imported item(s)
mod internal_lib; // Assuming the library name in `internal_lib.sw` is `internal_lib`
use internal_lib::mint;
// `mint` from `internal_library` is now available in this file
External Libraries
External libraries are located outside the main src directory as shown below:
$ tree
.
├── my_project
│ ├── Cargo.toml
│ ├── Forc.toml
│ └─── src
│ └── main.sw
│
└── external_lib
├── Cargo.toml
├── Forc.toml
└─── src
└── lib.sw
As external_lib is outside the src directory of my_project, it needs to be added as a dependency in the Forc.toml file of my_project, by adding the library path in the dependencies section as shown below, before it can be imported:
[dependencies]
external_library = { path = "../external_library" }
Once the library dependency is added to the toml file, you can import items from it as follows:
- Make sure the item you want imported are declared with the
pubkeyword (if applicable, for instance:pub fn mint() {}) - Use the
usekeyword to selectively import items from the library
use external_library::mint;
// `mint` from `external_library` is now available in this file
Wildcard imports using * are supported, but it is generally recommended to use explicit imports where possible.
Note: the standard library is implicitly available to all Forc projects, that is, you are not required to manually specify
stdas an explicit dependency inForc.toml.
Reference Sway Libraries
The repository sway-libs is a collection of external libraries that you can import and make use of in your Fuel applications. These libraries are meant to be implementations of common use-cases valuable for dapp development.
Some Sway Libraries to try out:
Example
You can import and use a Sway Library such as the Ownership library just like any other external library.
use ownership::Ownership;
Once imported, you can use the following basic functionality of the library in your smart contract:
- Declaring an owner
- Changing ownership
- Renouncing ownership
- Ensuring a function may only be called by the owner
Scripts
A script is runnable bytecode on the chain which executes once to perform some task. It does not represent ownership of any resources and it cannot be called by a contract. A script can return a single value of any type.
Scripts are state-aware in that while they have no persistent storage (because they only exist during the transaction) they can call contracts and act based upon the returned values and results.
This example script calls a contract:
script;
use wallet_abi::Wallet;
fn main() {
let contract_address = 0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b;
let caller = abi(Wallet, contract_address);
let amount_to_send = 200;
let recipient_address = Address::from(0x9299da6c73e6dc03eeabcce242bb347de3f5f56cd1c70926d76526d7ed199b8b);
caller
.send_funds {
gas: 10000,
coins: 0,
asset_id: b256::zero(),
}(amount_to_send, recipient_address);
}
Scripts, similar to predicates, rely on a main() function as an entry point. You can call other functions defined in a script from the main() function or call another contract via an ABI cast.
An example use case for a script would be a router that trades funds through multiple decentralized exchanges to get the price for the input asset, or a script to re-adjust a Collateralized Debt Position via a flash loan.
Scripts and the SDKs
Unlike EVM transactions which can call a contract directly (but can only call a single contract), Fuel transactions execute a script, which may call zero or more contracts. The Rust and TypeScript SDKs provide functions to call contract methods as if they were calling contracts directly. Under the hood, the SDKs wrap all contract calls with scripts that contain minimal code to simply make the call and forward script data as call parameters.
Predicates
From the perspective of Sway, predicates are programs that return a Boolean value and which represent ownership of some resource upon execution to true. They have no access to contract storage. Here is a trivial predicate, which always evaluates to true:
predicate;
// All predicates require a main function which returns a Boolean value.
fn main() -> bool {
true
}
The address of this predicate is 0xd19a5fe4cb9baf41ad9813f1a6fef551107c8e8e3f499a6e32bccbb954a74764. Any assets sent to this address can be unlocked or claimed by executing the predicate above as it always evaluates to true.
It does not need to be deployed to a blockchain because it only exists during a transaction. That being said, the predicate address is on-chain as the owner of one or more UTXOs.
Transfer Coins to a Predicate
In Fuel, coins can be sent to a predicate's address(the bytecode root, calculated here).
Spending Predicate Coins
The coin UTXOs become spendable not on the provision of a valid signature, but rather if the supplied predicate both has a root that matches their owner, and evaluates to true.
If a predicate reverts, or tries to access impure VM opcodes, the evaluation is automatically false.
An analogy for predicates is rather than a traditional 12 or 24 word seed phrase that generates a private key and creates a valid signature, a predicate's code can be viewed as the private key. Anyone with the code may execute a predicate, but only when the predicate evaluates to true may the assets owned by that address be released.
Spending Conditions
Predicates may introspect the transaction spending their coins (inputs, outputs, script bytecode, etc.) and may take runtime arguments, either or both of which may affect the evaluation of the predicate.
It is important to note that predicates cannot read or write memory. They may however check the inputs and outputs of a transaction. For example in the OTC Predicate Swap Example, a user may specify they would like to swap asset1 for asset2 and with amount of 5. The user would then send asset1 to the predicate. Only when the predicate can verify that the outputs include 5 coins of asset2 being sent to the original user, may asset1 be transferred out of the predicate.
Debugging Predicates
Because they don't have any side effects (they are pure), predicates cannot create receipts. Therefore, they cannot have logging or create a stack backtrace. This means that there is no native way to debug them aside from using a single-stepping debugger.
As a workaround, the predicate can be written, tested, and debugged first as a script, and then changed back into a predicate.
Sway Language basics
Sway is a programming language designed for the FuelVM. It is a statically typed, compiled language with type inference and traits. Sway aims to make smart contract development safer and more efficient through the use of strong static analysis and compiler feedback.
Get started with the basics of Sway:
- Variables
- Built-in Types
- Commonly Used Library Types
- Blockchain Types
- Functions
- Structs, Tuples, and Enums
- Methods and Associated Functions
- Constants
- Comments and Logging
- Control Flow
Variables
Variables in Sway are immutable by default. This means that, by default, once a variable is declared, its value cannot change. This is one of the ways how Sway encourages safe programming, and many modern languages have this same default.
Let's take a look at variables in detail.
Declaring a Variable
Let's look at a variable declaration:
let foo = 5;
Great! We have just declared a variable, foo. What do we know about foo?
- It is immutable.
- Its value is
5. - Its type is
u64, a 64-bit unsigned integer.
u64 is the default numeric type, and represents a 64-bit unsigned integer. See the section Built-in Types for more details.
We can also make a mutable variable. Let's take a look:
let mut foo = 5;
foo = 6;
Now, foo is mutable, and the reassignment to the number 6 is valid. That is, we are allowed to mutate the variable foo to change its value.
When assigning to a mutable variable, the right-hand side of the assignment is evaluated before the left-hand side. In the below example, the mutable variable i will first be increased and the resulting value of 1 will be stored to array[1], thus resulting in array being changed to [0, 1, 0].
let mut array = [0, 0, 0];
let mut i = 0;
array[i] = {
i += 1;
i
};
Type Annotations
A variable declaration can contain a type annotation. A type annotation serves the purpose of declaring the type, in addition to the value, of a variable.
Let's take a look:
let foo: u32 = 5;
We have just declared the type of the variable foo as a u32, which is an unsigned 32-bit integer. Let's take a look at a few other type annotations:
let bar: str[4] = __to_str_array("sway");
let baz: bool = true;
If the value declared cannot be assigned to the declared type, there will be an error generated by the compiler.
Built-in Types
Every value in Sway is of a certain type. Although deep down, all values are just ones and zeroes in the underlying virtual machine, Sway needs to know what those ones and zeroes actually mean. This is accomplished with types.
Sway is a statically typed language. At compile time, the types of every value must be known. This does not mean you need to specify every single type: usually, the type can be reasonably inferred by the compiler.
Primitive Types
Sway has the following primitive types:
()(unit type)u8(8-bit unsigned integer)u16(16-bit unsigned integer)u32(32-bit unsigned integer)u64(64-bit unsigned integer)u256(256-bit unsigned integer)str[](fixed-length string)str(string slices)bool(Booleantrueorfalse)b256(256 bits (32 bytes), i.e. a hash)
All other types in Sway are built up of these primitive types, or references to these primitive types. You may notice that there are no signed integers—this is by design. In the blockchain domain that Sway occupies, floating-point values and negative numbers have smaller utility, so their implementation has been left up to libraries for specific use cases.
Unit Type
The unit type, (), is a type that allows only one value, and thus, represents a value with no information. It is used to indicate the absence of a meaningful value, or the result of a function that performs an action, but does not return any data. The value of the unit type, called simply unit, has the same symbol as the unit type, (). Unit type in Sway serves a similar purpose as void in imperative languages like C or Java.
For example:
fn returns_unit() -> () { // Here, `()` represent the unit type.
() // Here, `()` represents the single unit value of the unit type.
}
In Sway, if the function return type is not specified, it is () by default. Thus, the above example is semantically same as the following:
fn returns_unit() {
}
Numeric Types
All of the unsigned integer types are numeric types.
Numbers can be declared with binary syntax, hexadecimal syntax, base-10 syntax, and underscores for delineation. Let's take a look at the following valid numeric primitives:
0xffffff // hexadecimal
0b10101010 // binary
10 // base-10
100_000 // underscore delineated base-10
0x1111_0000 // underscore delineated binary
0xfff_aaa // underscore delineated hexadecimal
The default numeric type is u64. The FuelVM's word size is 64 bits, and the cases where using a smaller numeric type saves space are minimal.
If a 64-bit or 256-bit arithmetic operation produces an overflow or an underflow, computation gets reverted automatically by FuelVM.
8/16/32-bit arithmetic operations are emulated using their 64-bit analogues with additional overflow/underflow checks inserted, which generally results in somewhat higher gas consumption.
The same does not happen with 256-bit operations, including b256, which uses specialized operations and are as efficient as possible.
Boolean Type
The boolean type (bool) has two potential values: true or false. Boolean values are typically used for conditional logic or validation, for example in if expressions. Booleans can be negated, or flipped, with the unary negation operator !.
For example:
fn returns_false() -> bool {
let boolean_value: bool = true;
!boolean_value
}
String Slices
In Sway, string literals are stored as variable length string slices. Which means that they are stored as a pointer to the actual string data and its length.
let my_string: str = "fuel";
String slices, because they contain pointers have limited usage. They cannot be used as constants, storage fields, or configurable constants, nor as main function arguments or returns.
For these cases one must use string arrays, as described below.
String Arrays
In Sway, static-length strings are a primitive type. This means that when you declare a string array, its size is a part of its type. This is necessary for the compiler to know how much memory to give for the storage of that data. The size of the string is denoted with square brackets.
Let's take a look:
let my_string: str[4] = __to_str_array("fuel");
Because the string literal "fuel" is four letters, the type is str[4], denoting a static length of 4 characters. Strings default to UTF-8 in Sway.
As above, string literals are typed as string slices. So that is why the need for __to_str_array that convert them to string arrays at compile time.
Conversion during runtime can be done with from_str_array and try_as_str_array. The latter can fail, given that the specified string array must be big enough for the string slice content.
let a: str = "abcd";
let b: str[4] = a.try_as_str_array().unwrap();
let c: str = from_str_array(b);
Compound Types
Compound types are types that group multiple values into one type. In Sway, we have arrays and tuples.
Tuple Types
A tuple is a general-purpose static-length aggregation of types. In more plain terms, a tuple is a single type that consists of an aggregate of zero or more types. The internal types that make up a tuple, and the tuple's arity, define the tuple's type.
Let's take a look at some examples.
let x: (u64, u64) = (0, 0);
This is a tuple, denoted by parenthesized, comma-separated values. Note that the type annotation, (u64, u64), is similar in syntax to the expression which instantiates that type, (0, 0).
let x: (u64, bool) = (42, true);
assert(x.1);
In this example, we have created a new tuple type, (u64, bool), which is a composite of a u64 and a bool.
To access a value within a tuple, we use tuple indexing: x.1 stands for the first (zero-indexed, so the bool) value of the tuple. Likewise, x.0 would be the zeroth, u64 value of the tuple. Tuple values can also be accessed via destructuring.
struct Foo {}
let x: (u64, Foo, bool) = (42, Foo {}, true);
let (number, foo, boolean) = x;
To create one-arity tuples, we will need to add a trailing comma:
let x: u64 = (42); // x is of type u64
let y: (u64) = (42); // y is of type u64
let z: (u64,) = (42,); // z is of type (u64), i.e. a one-arity tuple
let w: (u64) = (42,); // type error
Arrays
An array is similar to a tuple, but an array's values must all be of the same type. Arrays can hold arbitrary types including non-primitive types.
An array is written as a comma-separated list inside square brackets:
let x = [1, 2, 3, 4, 5];
Arrays are allocated on the stack since their size is known. An array's size is always static, i.e. it cannot change. An array of five elements cannot become an array of six elements.
Arrays can be iterated over, unlike tuples. An array's type is written as the type the array contains followed by the number of elements, semicolon-separated and within square brackets, e.g., [u64; 5]. To access an element in an array, use the array indexing syntax, i.e. square brackets.
Array elements can also be mutated if the underlying array is declared as mutable:
let mut x = [1, 2, 3, 4, 5];
x[0] = 0;
script;
struct Foo {
f1: u32,
f2: b256,
}
fn main() {
// Array of integers with type ascription
let array_of_integers: [u8; 5] = [1, 2, 3, 4, 5];
// Array of strings
let array_of_strings = ["Bob", "Jan", "Ron"];
// Array of structs
let array_of_structs: [Foo; 2] = [
Foo {
f1: 11,
f2: 0x1111111111111111111111111111111111111111111111111111111111111111,
},
Foo {
f1: 22,
f2: 0x2222222222222222222222222222222222222222222222222222222222222222,
},
];
// Accessing an element of an array
let mut array_of_bools: [bool; 2] = [true, false];
assert(array_of_bools[0]);
// Mutating the element of an array
array_of_bools[1] = true;
assert(array_of_bools[1]);
}
Commonly Used Library Types
The Sway Standard Library is the foundation of portable Sway software, a set of minimal shared abstractions for the broader Sway ecosystem. It offers core types, library-defined operations on language primitives, native asset management, blockchain contextual operations, access control, storage management, and support for types from other VMs, among many other things. Reference the standard library docs here.
Result<T, E>
Type Result is the type used for returning and propagating errors. It is an enum with two variants: Ok(T), representing success and containing a value, and Err(E), representing error and containing an error value. The T and E in this definition are type parameters, allowing Result to be generic and to be used with any types.
//! Error handling with the `Result` type.
//!
//! `Result<T, E>` `Result` is the type used for returning and propagating
//! errors. It is an enum with the variants, `Ok(T)`, representing
//! success and containing a value, and `Err(E)`, representing error
//! and containing an error value.
//!
//! Functions return `Result` whenever errors are expected and recoverable. In
//! the `std` crate, `Result` is most prominently used for `Identity`
//! interactions and cryptographic operations.
//!
//! A simple function returning `Result` might be defined and used like so:
//!
//! ```
//! enum Version {
//! Version1,
//! Version2,
//! }
//!
//! enum VersionError {
//! InvalidNumber,
//! }
//!
//! fn parse_version(version_number: u8) -> Result<Version, VersionError> {
//! match version_number {
//! 1 => Ok(Version::Version1),
//! 2 => Ok(Version::Version2),
//! _ => Err(VersionError::InvalidNumber),
//! }
//! }
//! ```
//!
//! ### Method overview
//!
//! In addition to working with pattern matching, `Result` provides a variety
//! of methods.
//!
//! ### Querying the variant
//!
//! The `is_ok` and `is_err` methods return `true` if the `Result` is
//! `Ok` or `Err`, respectively.
//!
//! `is_ok` : `Result::is_ok`
//! `is_err`: `Result::is_err`
//!
//! ### Extracting the contained value
//!
//! These methods extract the contained value in a `Result<T,E>` when it is
//! the `Ok` variant. If the `Result` is `Err`:
//!
//! * `unwrap` reverts.
//! * `unwrap_or` returns the default provided value.
//!
//! `unwrap` : `Result::unwrap`
//! `unwrap_or`: `Result::unwrap_or`
library;
use ::logging::log;
use ::revert::revert;
use ::codec::*;
use ::ops::*;
// ANCHOR: docs_result
/// `Result` is a type that represents either success (`Ok`) or failure (`Err`).
pub enum Result<T, E> {
/// Contains the success value.
Ok: T,
/// Contains the error value.
Err: E,
}
// ANCHOR_END: docs_result
// Type implementation
//
impl<T, E> Result<T, E> {
// Querying the contained values
//
/// Returns whether a result contains a success value.
///
/// # Returns
///
/// * [bool] - Returns `true` if the result is `Ok`.
///
/// # Examples
///
/// ```sway
/// enum Error {
/// NotFound,
/// Invalid,
/// }
///
/// fn foo() {
/// let x: Result<u64, Error> = Result::Ok(42);
/// assert(x.is_ok());
///
/// let y: Result<u64, Error> = Result::Err(Error::NotFound));
/// assert(!y.is_ok());
/// }
/// ```
pub fn is_ok(self) -> bool {
match self {
Self::Ok(_) => true,
_ => false,
}
}
/// Returns whether a result contains an error value.
///
/// # Returns
///
/// * [bool] - Returns `true` if the result is `Err`.
///
/// # Examples
///
/// ```sway
/// enum Error {
/// NotFound,
/// Invalid,
/// }
///
/// fn foo() {
/// let x: Result<u64, Error> = Result::Ok(42);
/// assert(!x.is_err());
///
/// let y: Result<u64, Error> = Result::Err(Error::NotFound));
/// assert(y.is_err());
/// }
/// ```
pub fn is_err(self) -> bool {
match self {
Self::Ok(_) => false,
_ => true,
}
}
/// Returns the contained `Ok` value, consuming the `self` value.
///
/// # Additional Information
///
/// Because this function may revert, its use is generally discouraged.
/// Instead, prefer to use pattern matching and handle the `Err`
/// case explicitly.
///
/// # Returns
///
/// * [T] - The value contained by the result.
///
/// # Reverts
///
/// * Reverts if the `Result` is the `Err` variant.
///
/// # Examples
///
/// ```sway
/// enum Error {
/// NotFound,
/// Invalid,
/// }
///
/// fn foo() {
/// let x: Result<u64, Error> = Result::Ok(42);
/// assert(x.unwrap() == 42);
///
/// let y: Result<u64, Error> = Result::Err(Error::NotFound));
/// let val = y.unwrap(); // reverts
/// }
/// ```
pub fn unwrap(self) -> T {
match self {
Self::Ok(inner_value) => inner_value,
_ => revert(0),
}
}
/// Returns the contained `Ok` value or a provided default.
///
/// # Arguments
///
/// * `default`: [T] - The value that is the default.
///
/// # Returns
///
/// * [T] - The value of the result or the default.
///
/// # Examples
///
/// ```sway
/// enum Error {
/// NotFound,
/// Invalid,
/// }
///
/// fn foo() {
/// let x: Result<u64, Error> = Result::Ok(42);
/// assert(x.unwrap_or(69) == 42);
///
/// let y: Result<u64, Error> = Result::Err(Error::NotFound));
/// assert(y.unwrap_or(69) == 69);
/// }
/// ```
pub fn unwrap_or(self, default: T) -> T {
match self {
Self::Ok(inner_value) => inner_value,
Self::Err(_) => default,
}
}
/// Returns the contained `Ok` value, consuming the `self` value.
/// If the `Result` is the `Err` variant, logs the provided message, along with the error value.
///
/// # Additional Information
///
/// Because this function may revert, its use is generally discouraged.
/// Instead, prefer to use pattern matching and handle the `Err`
/// case explicitly.
///
/// # Arguments
///
/// * `msg`: [M] - The message to be logged if the `Result` is the `Err` variant.
///
/// # Returns
///
/// * [T] - The value contained by the result.
///
/// # Reverts
///
/// * Reverts if the `Result` is the `Err` variant.
///
/// # Examples
///
/// ```sway
/// enum Error {
/// NotFound,
/// Invalid,
/// }
///
/// fn foo() {
/// let x: Result<u64, Error> = Result::Ok(42);
/// assert(x.expect("X is known to be 42") == 42);
///
/// let y: Result<u64, Error> = Result::Err(Error::NotFound));
/// let val = y.expect("Testing expect"); // reverts with `("Testing Expect", "Error::NotFound")`
/// }
/// ```
///
/// # Recommended Message Style
///
/// We recommend that `expect` messages are used to describe the reason you *expect* the `Result` should be `Ok`.
///
/// ```sway
/// let x: Result<u64, Error> = bar(1);
/// let value = x.expect("bar() should never return Err with 1 as an argument");
/// ```
pub fn expect<M>(self, msg: M) -> T
where
M: AbiEncode,
E: AbiEncode,
{
match self {
Self::Ok(v) => v,
Self::Err(err) => {
log((msg, err));
revert(0);
},
}
}
// TODO: Implement the following transforms when Option and Result can
// import one another:
// - `ok(self) -> Option<T>`
// - `err(self) -> Option<E>`
}
impl<T, E> PartialEq for Result<T, E>
where
T: PartialEq,
E: PartialEq,
{
fn eq(self, other: Self) -> bool {
match (self, other) {
(Self::Ok(a), Self::Ok(b)) => a == b,
(Self::Err(a), Self::Err(b)) => a == b,
_ => false,
}
}
}
impl<T, E> Eq for Result<T, E>
where
T: Eq,
E: Eq,
{}
Functions return Result whenever errors are expected and recoverable.
Take the following example:
script;
enum MyContractError {
DivisionByZero: (),
}
fn divide(numerator: u64, denominator: u64) -> Result<u64, MyContractError> {
if (denominator == 0) {
return Err(MyContractError::DivisionByZero);
} else {
Ok(numerator / denominator)
}
}
fn main() -> Result<u64, str[4]> {
let result = divide(20, 2);
match result {
Ok(value) => Ok(value),
Err(MyContractError::DivisionByZero) => Err(__to_str_array("Fail")),
}
}
Option<T>
Type Option represents an optional value: every Option is either Some and contains a value, or None, and does not. Option types are very common in Sway code, as they have a number of uses:
- Initial values where
Nonecan be used as an initializer. - Return value for otherwise reporting simple errors, where
Noneis returned on error.
The implementation of Option matches on the variant: if it's Ok it returns the inner value, if it's None, it reverts.
//! A type for optional values.
//!
//! Type `Option` represents an optional value: every `Option`
//! is either `Some` and contains a value, or `None`, and
//! does not. `Option` types are very common in Sway code, as
//! they have a number of uses:
//!
//! * Initial values where `None` can be used as an initializer.
//! * Return value for otherwise reporting simple errors, where `None` is
//! returned on error.
//! * Optional struct fields.
//! * Optional function arguments.
//!
//! `Option`s are commonly paired with pattern matching to query the presence
//! of a value and take action, always accounting for the `None` case.
//!
//! ```
//! fn divide(numerator: u64, denominator: u64) -> Option<u64> {
//! if denominator == 0 {
//! None
//! } else {
//! Some(numerator / denominator)
//! }
//! }
//!
//! fn call_divide() {
//! // The return value of the function is an option
//! let result = divide(6, 2);
//!
//! // Pattern match to retrieve the value
//! match result {
//! // The division was valid
//! Some(x) => std::logging::log(x),
//! // The division was invalid
//! None => std::logging::log("Cannot divide by 0"),
//! }
//! }
//! ```
//!
//! # Method overview
//!
//! In addition to working with pattern matching, `Option` provides a wide
//! variety of different methods.
//!
//! # Querying the variant
//!
//! The `is_some` and `is_none` methods return `true` if the `Option`
//! is `Some` or `None`, respectively.
//!
//! `is_none`: `Option::is_none`
//! `is_some`: `Option::is_some`
//!
//! # Extracting the contained value
//!
//! These methods extract the contained value in an `Option<T>` when it
//! is the `Some` variant. If the `Option` is `None`:
//!
//! * `unwrap` reverts.
//! * `unwrap_or` returns the provided default value.
//!
//! `unwrap` : `Option::unwrap`
//! `unwrap_or`: `Option::unwrap_or`
//!
//! # Transforming contained values
//!
//! These methods transform `Option` to `Result`:
//!
//! * `ok_or` transforms `Some(v)` to `Ok(v)`, and `None` to
//! `Err(e)` using the provided default error value.
//!
//! `Err(e)` : `Result::Err`
//! `Ok(v)` : `Result::Ok`
//! `Some(v)`: `Option::Some`
//! `ok_or` : `Option::ok_or`
library;
use ::logging::log;
use ::result::Result;
use ::revert::revert;
use ::codec::*;
use ::ops::*;
// ANCHOR: docs_option
/// A type that represents an optional value, either `Some(val)` or `None`.
pub enum Option<T> {
/// No value.
None: (),
/// Some value of type `T`.
Some: T,
}
// ANCHOR_END: docs_option
impl<T> PartialEq for Option<T>
where
T: PartialEq,
{
fn eq(self, other: Self) -> bool {
match (self, other) {
(Option::Some(a), Option::Some(b)) => a == b,
(Option::None, Option::None) => true,
_ => false,
}
}
}
impl<T> Eq for Option<T>
where
T: Eq,
{}
// Type implementation
//
impl<T> Option<T> {
// Querying the contained values
//
/// Returns whether the option is the `Some` variant.
///
/// # Returns
///
/// * [bool] - Returns `true` if the option is `Some`, otherwise `false`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let x: Option<u32> = Some(2);
/// assert(x.is_some());
///
/// let x: Option<u32> = None;
/// assert(!x.is_some());
/// }
/// ```
pub fn is_some(self) -> bool {
match self {
Self::Some(_) => true,
_ => false,
}
}
/// Returns whether the option is the `None` variant.
///
/// # Returns
///
/// * [bool] - Returns `true` if the option is `None`, otherwise `false`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let x: Option<u32> = Some(2);
/// assert(!x.is_none());
///
/// let x: Option<u32> = None;
/// assert(x.is_none());
/// }
/// ```
pub fn is_none(self) -> bool {
match self {
Self::Some(_) => false,
_ => true,
}
}
// Getting to contained values
//
/// Returns the contained `Some` value, consuming the `self` value.
///
/// # Additional Information
///
/// Because this function may revert, its use is generally discouraged.
/// Instead, use pattern matching and handle the `None`
/// case explicitly, or call `unwrap_or`.
///
/// # Returns
///
/// * [T] - The value contained by the option.
///
/// # Reverts
///
/// * Reverts if the `Option` is the `None` variant.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let x = Some(42);
/// assert(x.unwrap() == 42);
/// }
/// ```
///
/// ```sway
/// fn foo() {
/// let x: Option<u64> = None;
/// let value = x.unwrap(); // reverts
/// }
/// ```
pub fn unwrap(self) -> T {
match self {
Self::Some(inner_value) => inner_value,
_ => revert(0),
}
}
/// Returns the contained `Some` value or a provided default.
///
/// # Arguments
///
/// * `default`: [T] - The default value the function will revert to.
///
/// # Returns
///
/// * [T] - The contained value or the default value.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// assert(Some(42).unwrap_or(69) == 42);
/// assert(None::<u64>().unwrap_or(69) == 69);
/// }
/// ```
pub fn unwrap_or(self, default: T) -> T {
match self {
Self::Some(x) => x,
Self::None => default,
}
}
// Transforming contained values
//
/// Transforms the `Option<T>` into a `Result<T, E>`, mapping `Some(v)` to
/// `Ok(v)` and `None` to `Err(e)`.
///
/// # Additional Information
///
/// `Ok(v)` : `Result::Ok`
/// `Err(e)` : `Result::Err`
/// `Some(v)`: `Option::Some`
/// `ok_or` : `Option::ok_or`
///
/// # Arguments
///
/// * `err`: [E] - The error value if the option is `None`.
///
/// # Returns
///
/// * [Result<T, E>] - The result containing the value or the error.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let x = Some(42);
/// match x.ok_or(0) {
/// Result::Ok(inner) => assert(inner == 42),
/// Result::Err => revert(0),
/// }
///
/// let x: Option<u64> = None;
/// match x.ok_or(0) {
/// Result::Ok(_) => revert(0),
/// Result::Err(e) => assert(e == 0),
/// }
/// }
/// ```
pub fn ok_or<E>(self, err: E) -> Result<T, E> {
match self {
Self::Some(v) => Result::Ok(v),
Self::None => Result::Err(err),
}
}
/// Returns the contained `Some` value, consuming the `self` value.
/// If the `Option` is the `None` variant, logs the provided message.
///
/// # Additional Information
///
/// Because this function may revert, its use is generally discouraged.
/// Instead, prefer to use pattern matching and handle the `None`
/// case explicitly.
///
/// # Arguments
///
/// * `msg`: [M] - The message to be logged if the `Option` is the `None` variant.
///
/// # Returns
///
/// * [T] - The value contained by the option.
///
/// # Reverts
///
/// * Reverts if the `Option` is the `None` variant.
///
/// # Examples
///
/// ```sway
///
/// fn foo() {
/// let x: Option<u64> = Some(42);
/// assert(x.expect("X is known to be 42") == 42);
///
/// let y: Option<u64> = None;
/// let val = y.expect("Testing expect"); // reverts with `("Testing Expect")`
/// }
/// ```
///
/// # Recommended Message Style
///
/// We recommend that `expect` messages are used to describe the reason you *expect* the `Option` should be `Some`.
///
/// ```sway
/// let x: Option<u64> = bar(1);
/// let value = x.expect("bar() should never return None with 1 as an argument");
/// ```
pub fn expect<M>(self, msg: M) -> T
where
M: AbiEncode,
{
match self {
Self::Some(v) => v,
Self::None => {
log(msg);
revert(0);
},
}
}
}
Option is commonly paired with pattern matching to query the presence of a value and take action, allowing developers to choose how to handle the None case.
Below is an example that uses pattern matching to handle invalid divisions by 0 by returning an Option:
script;
fn divide(numerator: u64, denominator: u64) -> Option<u64> {
if denominator == 0 {
None
} else {
Some(numerator / denominator)
}
}
fn main() {
let result = divide(6, 2);
// Pattern match to retrieve the value
match result {
// The division was valid
Some(x) => std::logging::log(x),
// The division was invalid
None => std::logging::log("Cannot divide by 0"),
}
}
Blockchain Types
Sway is fundamentally a blockchain language, and it offers a selection of types tailored for the blockchain use case.
These are provided via the standard library (lib-std) which both add a degree of type-safety, as well as make the intention of the developer more clear.
Address Type
The Address type is a type-safe wrapper around the primitive b256 type. Unlike the EVM, an address never refers to a deployed smart contract (see the ContractId type below). An Address can be either the hash of a public key (effectively an externally owned account if you're coming from the EVM) or the hash of a predicate. Addresses own UTXOs.
An Address is implemented as follows.
pub struct Address {
value: b256,
}
Casting between the b256 and Address types must be done explicitly:
let my_number: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let my_address: Address = Address::from(my_number);
let forty_two: b256 = my_address.into();
ContractId Type
The ContractId type is a type-safe wrapper around the primitive b256 type. A contract's ID is a unique, deterministic identifier analogous to a contract's address in the EVM. Contracts cannot own UTXOs but can own assets.
A ContractId is implemented as follows.
pub struct ContractId {
value: b256,
}
Casting between the b256 and ContractId types must be done explicitly:
let my_number: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let my_contract_id: ContractId = ContractId::from(my_number);
let forty_two: b256 = my_contract_id.into();
Getting a Contract's ContractId
To get the ContractId of a contract in an internal context use the ContractId::this() function:
impl MyContract for Contract {
fn foo() {
let this_contract_id: ContractId = ContractId::this();
}
}
Identity Type
The Identity type is an enum that allows for the handling of both Address and ContractId types. This is useful in cases where either type is accepted, e.g., receiving funds from an identified sender, but not caring if the sender is an address or a contract.
An Identity is implemented as follows.
//! A wrapper type with two variants, `Address` and `ContractId`.
//! The use of this type allows for handling interactions with contracts and addresses in a unified manner.
library;
use ::codec::*;
use ::assert::assert;
use ::address::Address;
use ::alias::SubId;
use ::asset_id::AssetId;
use ::contract_id::ContractId;
use ::hash::{Hash, Hasher};
use ::option::Option::{self, *};
use ::ops::*;
/// The `Identity` type: either an `Address` or a `ContractId`.
// ANCHOR: docs_identity
pub enum Identity {
Address: Address,
ContractId: ContractId,
}
// ANCHOR_END: docs_identity
impl PartialEq for Identity {
fn eq(self, other: Self) -> bool {
match (self, other) {
(Identity::Address(addr1), Identity::Address(addr2)) => addr1 == addr2,
(Identity::ContractId(id1), Identity::ContractId(id2)) => id1 == id2,
_ => false,
}
}
}
impl Eq for Identity {}
impl Identity {
/// Returns the `Address` of the `Identity`.
///
/// # Returns
///
/// * [Option<Address>] - `Some(Address)` if the underlying type is an `Address`, otherwise `None`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let identity = Identity::Address(Address::zero());
/// let address = identity.as_address();
/// assert(address == Address::zero());
/// }
/// ```
pub fn as_address(self) -> Option<Address> {
match self {
Self::Address(addr) => Some(addr),
Self::ContractId(_) => None,
}
}
/// Returns the `ContractId` of the `Identity`.
///
/// # Returns
///
/// * [Option<ContractId>] - `Some(Contract)` if the underlying type is an `ContractId`, otherwise `None`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let identity = Identity::ContractId(ContractId::zero());
/// let contract_id = identity.as_contract_id();
/// assert(contract_id == ContractId::zero());
/// }
/// ```
pub fn as_contract_id(self) -> Option<ContractId> {
match self {
Self::Address(_) => None,
Self::ContractId(id) => Some(id),
}
}
/// Returns whether the `Identity` represents an `Address`.
///
/// # Returns
///
/// * [bool] - Indicates whether the `Identity` holds an `Address`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let identity = Identity::Address(Address::zero());
/// assert(identity.is_address());
/// }
/// ```
pub fn is_address(self) -> bool {
match self {
Self::Address(_) => true,
Self::ContractId(_) => false,
}
}
/// Returns whether the `Identity` represents a `ContractId`.
///
/// # Returns
///
/// * [bool] - Indicates whether the `Identity` holds a `ContractId`.
///
/// # Examples
///
/// ```sway
/// fn foo() {
/// let identity = Identity::ContractId(ContractId::zero());
/// assert(identity.is_contract_id());
/// }
/// ```
pub fn is_contract_id(self) -> bool {
match self {
Self::Address(_) => false,
Self::ContractId(_) => true,
}
}
/// Returns the underlying raw `b256` data of the identity.
///
/// # Returns
///
/// * [b256] - The raw data of the identity.
///
/// # Examples
///
/// ```sway
/// fn foo() -> {
/// let my_identity = Identity::Address(Address::zero());
/// assert(my_identity.bits() == b256::zero());
/// }
/// ```
pub fn bits(self) -> b256 {
match self {
Self::Address(address) => address.bits(),
Self::ContractId(contract_id) => contract_id.bits(),
}
}
}
impl Hash for Identity {
fn hash(self, ref mut state: Hasher) {
match self {
Identity::Address(address) => {
0_u8.hash(state);
address.hash(state);
},
Identity::ContractId(id) => {
1_u8.hash(state);
id.hash(state);
},
}
}
}
Casting to an Identity must be done explicitly:
contract;
mod r#abi;
mod errors;
use abi::IdentityExample;
use errors::MyError;
use std::asset::transfer;
storage {
owner: Identity = Identity::ContractId(ContractId::zero()),
}
impl IdentityExample for Contract {
fn cast_to_identity() {
// ANCHOR: cast_to_identity
let raw_address: b256 = 0xddec0e7e6a9a4a4e3e57d08d080d71a299c628a46bc609aab4627695679421ca;
let my_identity: Identity = Identity::Address(Address::from(raw_address));
// ANCHOR_END: cast_to_identity
}
fn identity_to_contract_id(my_identity: Identity) {
// ANCHOR: identity_to_contract_id
let my_contract_id: ContractId = match my_identity {
Identity::ContractId(identity) => identity,
_ => revert(0),
};
// ANCHOR_END: identity_to_contract_id
}
fn different_executions(my_identity: Identity) {
// ANCHOR: different_executions
match my_identity {
Identity::Address(address) => takes_address(address),
Identity::ContractId(contract_id) => takes_contract_id(contract_id),
};
// ANCHOR_END: different_executions
}
#[storage(read)]
fn access_control_with_identity() {
// ANCHOR: access_control_with_identity
let sender = msg_sender().unwrap();
require(
sender == storage
.owner
.read(),
MyError::UnauthorizedUser(sender),
);
// ANCHOR_END: access_control_with_identity
}
}
fn takes_address(address: Address) {}
fn takes_contract_id(contract_id: ContractId) {}
A match statement can be used to return to an Address or ContractId as well as handle cases in which their execution differs.
contract;
mod r#abi;
mod errors;
use abi::IdentityExample;
use errors::MyError;
use std::asset::transfer;
storage {
owner: Identity = Identity::ContractId(ContractId::zero()),
}
impl IdentityExample for Contract {
fn cast_to_identity() {
// ANCHOR: cast_to_identity
let raw_address: b256 = 0xddec0e7e6a9a4a4e3e57d08d080d71a299c628a46bc609aab4627695679421ca;
let my_identity: Identity = Identity::Address(Address::from(raw_address));
// ANCHOR_END: cast_to_identity
}
fn identity_to_contract_id(my_identity: Identity) {
// ANCHOR: identity_to_contract_id
let my_contract_id: ContractId = match my_identity {
Identity::ContractId(identity) => identity,
_ => revert(0),
};
// ANCHOR_END: identity_to_contract_id
}
fn different_executions(my_identity: Identity) {
// ANCHOR: different_executions
match my_identity {
Identity::Address(address) => takes_address(address),
Identity::ContractId(contract_id) => takes_contract_id(contract_id),
};
// ANCHOR_END: different_executions
}
#[storage(read)]
fn access_control_with_identity() {
// ANCHOR: access_control_with_identity
let sender = msg_sender().unwrap();
require(
sender == storage
.owner
.read(),
MyError::UnauthorizedUser(sender),
);
// ANCHOR_END: access_control_with_identity
}
}
fn takes_address(address: Address) {}
fn takes_contract_id(contract_id: ContractId) {}
contract;
mod r#abi;
mod errors;
use abi::IdentityExample;
use errors::MyError;
use std::asset::transfer;
storage {
owner: Identity = Identity::ContractId(ContractId::zero()),
}
impl IdentityExample for Contract {
fn cast_to_identity() {
// ANCHOR: cast_to_identity
let raw_address: b256 = 0xddec0e7e6a9a4a4e3e57d08d080d71a299c628a46bc609aab4627695679421ca;
let my_identity: Identity = Identity::Address(Address::from(raw_address));
// ANCHOR_END: cast_to_identity
}
fn identity_to_contract_id(my_identity: Identity) {
// ANCHOR: identity_to_contract_id
let my_contract_id: ContractId = match my_identity {
Identity::ContractId(identity) => identity,
_ => revert(0),
};
// ANCHOR_END: identity_to_contract_id
}
fn different_executions(my_identity: Identity) {
// ANCHOR: different_executions
match my_identity {
Identity::Address(address) => takes_address(address),
Identity::ContractId(contract_id) => takes_contract_id(contract_id),
};
// ANCHOR_END: different_executions
}
#[storage(read)]
fn access_control_with_identity() {
// ANCHOR: access_control_with_identity
let sender = msg_sender().unwrap();
require(
sender == storage
.owner
.read(),
MyError::UnauthorizedUser(sender),
);
// ANCHOR_END: access_control_with_identity
}
}
fn takes_address(address: Address) {}
fn takes_contract_id(contract_id: ContractId) {}
A common use case for Identity is for access control. The use of Identity uniquely allows both ContractId and Address to have access control inclusively.
contract;
mod r#abi;
mod errors;
use abi::IdentityExample;
use errors::MyError;
use std::asset::transfer;
storage {
owner: Identity = Identity::ContractId(ContractId::zero()),
}
impl IdentityExample for Contract {
fn cast_to_identity() {
// ANCHOR: cast_to_identity
let raw_address: b256 = 0xddec0e7e6a9a4a4e3e57d08d080d71a299c628a46bc609aab4627695679421ca;
let my_identity: Identity = Identity::Address(Address::from(raw_address));
// ANCHOR_END: cast_to_identity
}
fn identity_to_contract_id(my_identity: Identity) {
// ANCHOR: identity_to_contract_id
let my_contract_id: ContractId = match my_identity {
Identity::ContractId(identity) => identity,
_ => revert(0),
};
// ANCHOR_END: identity_to_contract_id
}
fn different_executions(my_identity: Identity) {
// ANCHOR: different_executions
match my_identity {
Identity::Address(address) => takes_address(address),
Identity::ContractId(contract_id) => takes_contract_id(contract_id),
};
// ANCHOR_END: different_executions
}
#[storage(read)]
fn access_control_with_identity() {
// ANCHOR: access_control_with_identity
let sender = msg_sender().unwrap();
require(
sender == storage
.owner
.read(),
MyError::UnauthorizedUser(sender),
);
// ANCHOR_END: access_control_with_identity
}
}
fn takes_address(address: Address) {}
fn takes_contract_id(contract_id: ContractId) {}
Converting Types
Below are some common type conversions in Sway:
Identity Conversions
Convert to Identity
library;
pub fn convert_to_identity() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// ANCHOR: convert_b256_to_address_or_contract_id
let address_from_b256: Address = Address::from(b256_address);
let contract_id_from_b256: ContractId = ContractId::from(b256_address);
// ANCHOR_END: convert_b256_to_address_or_contract_id
let address = address_from_b256;
let contract_id = contract_id_from_b256;
// ANCHOR: convert_to_identity
let identity_from_b256: Identity = Identity::Address(Address::from(b256_address));
let identity_from_address: Identity = Identity::Address(address);
let identity_from_contract_id: Identity = Identity::ContractId(contract_id);
// ANCHOR_END: convert_to_identity
}
pub fn convert_from_identity(my_identity: Identity) {
// ANCHOR: convert_from_identity
match my_identity {
Identity::Address(address) => log(address),
Identity::ContractId(contract_id) => log(contract_id),
};
// ANCHOR_END: convert_from_identity
}
pub fn convert_to_b256() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let address: Address = Address::from(b256_address);
let contract_id: ContractId = ContractId::from(b256_address);
// ANCHOR: convert_to_b256
let b256_from_address: b256 = address.into();
let b256_from_contract_id: b256 = contract_id.into();
// ANCHOR_END: convert_to_b256
}
Convert Identity to ContractId or Address
library;
pub fn convert_to_identity() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// ANCHOR: convert_b256_to_address_or_contract_id
let address_from_b256: Address = Address::from(b256_address);
let contract_id_from_b256: ContractId = ContractId::from(b256_address);
// ANCHOR_END: convert_b256_to_address_or_contract_id
let address = address_from_b256;
let contract_id = contract_id_from_b256;
// ANCHOR: convert_to_identity
let identity_from_b256: Identity = Identity::Address(Address::from(b256_address));
let identity_from_address: Identity = Identity::Address(address);
let identity_from_contract_id: Identity = Identity::ContractId(contract_id);
// ANCHOR_END: convert_to_identity
}
pub fn convert_from_identity(my_identity: Identity) {
// ANCHOR: convert_from_identity
match my_identity {
Identity::Address(address) => log(address),
Identity::ContractId(contract_id) => log(contract_id),
};
// ANCHOR_END: convert_from_identity
}
pub fn convert_to_b256() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let address: Address = Address::from(b256_address);
let contract_id: ContractId = ContractId::from(b256_address);
// ANCHOR: convert_to_b256
let b256_from_address: b256 = address.into();
let b256_from_contract_id: b256 = contract_id.into();
// ANCHOR_END: convert_to_b256
}
Convert ContractId or Address to b256
library;
pub fn convert_to_identity() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// ANCHOR: convert_b256_to_address_or_contract_id
let address_from_b256: Address = Address::from(b256_address);
let contract_id_from_b256: ContractId = ContractId::from(b256_address);
// ANCHOR_END: convert_b256_to_address_or_contract_id
let address = address_from_b256;
let contract_id = contract_id_from_b256;
// ANCHOR: convert_to_identity
let identity_from_b256: Identity = Identity::Address(Address::from(b256_address));
let identity_from_address: Identity = Identity::Address(address);
let identity_from_contract_id: Identity = Identity::ContractId(contract_id);
// ANCHOR_END: convert_to_identity
}
pub fn convert_from_identity(my_identity: Identity) {
// ANCHOR: convert_from_identity
match my_identity {
Identity::Address(address) => log(address),
Identity::ContractId(contract_id) => log(contract_id),
};
// ANCHOR_END: convert_from_identity
}
pub fn convert_to_b256() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let address: Address = Address::from(b256_address);
let contract_id: ContractId = ContractId::from(b256_address);
// ANCHOR: convert_to_b256
let b256_from_address: b256 = address.into();
let b256_from_contract_id: b256 = contract_id.into();
// ANCHOR_END: convert_to_b256
}
Convert b256 to ContractId or Address
library;
pub fn convert_to_identity() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// ANCHOR: convert_b256_to_address_or_contract_id
let address_from_b256: Address = Address::from(b256_address);
let contract_id_from_b256: ContractId = ContractId::from(b256_address);
// ANCHOR_END: convert_b256_to_address_or_contract_id
let address = address_from_b256;
let contract_id = contract_id_from_b256;
// ANCHOR: convert_to_identity
let identity_from_b256: Identity = Identity::Address(Address::from(b256_address));
let identity_from_address: Identity = Identity::Address(address);
let identity_from_contract_id: Identity = Identity::ContractId(contract_id);
// ANCHOR_END: convert_to_identity
}
pub fn convert_from_identity(my_identity: Identity) {
// ANCHOR: convert_from_identity
match my_identity {
Identity::Address(address) => log(address),
Identity::ContractId(contract_id) => log(contract_id),
};
// ANCHOR_END: convert_from_identity
}
pub fn convert_to_b256() {
let b256_address: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
let address: Address = Address::from(b256_address);
let contract_id: ContractId = ContractId::from(b256_address);
// ANCHOR: convert_to_b256
let b256_from_address: b256 = address.into();
let b256_from_contract_id: b256 = contract_id.into();
// ANCHOR_END: convert_to_b256
}
String Conversions
Convert str to str[]
library;
pub fn convert_str_to_str_array() {
// ANCHOR: str_to_str_array
let fuel_str: str = "fuel";
let fuel_str_array: str[4] = fuel_str.try_as_str_array().unwrap();
// ANCHOR_END: str_to_str_array
}
pub fn convert_str_array_to_str() {
// ANCHOR: str_array_to_str
let fuel_str_array: str[4] = __to_str_array("fuel");
let fuel_str: str = from_str_array(fuel_str_array);
// ANCHOR_END: str_array_to_str
}
Convert str[] to str
library;
pub fn convert_str_to_str_array() {
// ANCHOR: str_to_str_array
let fuel_str: str = "fuel";
let fuel_str_array: str[4] = fuel_str.try_as_str_array().unwrap();
// ANCHOR_END: str_to_str_array
}
pub fn convert_str_array_to_str() {
// ANCHOR: str_array_to_str
let fuel_str_array: str[4] = __to_str_array("fuel");
let fuel_str: str = from_str_array(fuel_str_array);
// ANCHOR_END: str_array_to_str
}
Number Conversions
Convert to u256
library;
pub fn convert_to_u256() {
// Convert any unsigned integer to `u256`
// ANCHOR: to_u256
let u8_1: u8 = 2u8;
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2u64;
let b256_1: b256 = 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20;
let u256_from_u8: u256 = u8_1.as_u256();
let u256_from_u16: u256 = u16_1.as_u256();
let u256_from_u32: u256 = u32_1.as_u256();
let u256_from_u64: u256 = u64_1.as_u256();
let u256_from_b256: u256 = b256_1.as_u256();
// ANCHOR_END: to_u256
}
Convert to u64
library;
pub fn convert_uint_to_u64() {
// Convert any unsigned integer to `u64`
// ANCHOR: to_u64
let u8_1: u8 = 2u8;
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let u64_from_u8: u64 = u8_1.as_u64();
let u64_from_u16: u64 = u16_1.as_u64();
let u64_from_u32: u64 = u32_1.as_u64();
let u64_from_u256: Option<u64> = <u64 as TryFrom<u256>>::try_from(u256_1);
// ANCHOR_END: to_u64
}
Convert to u32
library;
pub fn convert_uint_to_u32() {
// Convert any unsigned integer to `u32`
// ANCHOR: to_u32
let u8_1: u8 = 2u8;
let u16_1: u16 = 2u16;
let u64_1: u64 = 2;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let u32_from_u8: u32 = u8_1.as_u32();
let u32_from_u16: u32 = u16_1.as_u32();
let u32_from_u64_1: Option<u32> = u64_1.try_as_u32();
let u32_from_u64_2: Option<u32> = <u32 as TryFrom<u64>>::try_from(u64_1);
let u32_from_u256: Option<u32> = <u32 as TryFrom<u256>>::try_from(u256_1);
// ANCHOR_END: to_u32
}
Convert to u16
library;
pub fn convert_uint_to_u16() {
// Convert any unsigned integer to `u16`
// ANCHOR: to_u16
let u8_1: u8 = 2u8;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let u16_from_u8: u16 = u8_1.as_u16();
let u16_from_u32_1: Option<u16> = u32_1.try_as_u16();
let u16_from_u32_2: Option<u16> = <u16 as TryFrom<u32>>::try_from(u32_1);
let u16_from_u64_1: Option<u16> = u64_1.try_as_u16();
let u16_from_u64_2: Option<u16> = <u16 as TryFrom<u64>>::try_from(u64_1);
let u16_from_u256: Option<u16> = <u16 as TryFrom<u256>>::try_from(u256_1);
// ANCHOR_END: to_u16
}
Convert to u8
library;
pub fn convert_uint_to_u8() {
// Convert any unsigned integer to `u8`
// ANCHOR: to_u8
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let u8_from_u16_1: Option<u8> = u16_1.try_as_u8();
let u8_from_u16_2: Option<u8> = <u8 as TryFrom<u16>>::try_from(u16_1);
let u8_from_u32_1: Option<u8> = u32_1.try_as_u8();
let u8_from_u32_2: Option<u8> = <u8 as TryFrom<u32>>::try_from(u32_1);
let u8_from_u64_1: Option<u8> = u64_1.try_as_u8();
let u8_from_u64_2: Option<u8> = <u8 as TryFrom<u64>>::try_from(u64_1);
let u8_from_u256: Option<u8> = <u8 as TryFrom<u256>>::try_from(u256_1);
// ANCHOR_END: to_u8
assert(u8_from_u16_1.unwrap() == 2u8);
assert(u8_from_u16_2.unwrap() == 2u8);
assert(u8_from_u32_1.unwrap() == 2u8);
assert(u8_from_u32_2.unwrap() == 2u8);
assert(u8_from_u64_1.unwrap() == 2u8);
assert(u8_from_u64_2.unwrap() == 2u8);
assert(u8_from_u256.unwrap() == 2u8);
}
Convert to Bytes
library;
// ANCHOR: to_bytes_import
use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}};
// ANCHOR_END: to_bytes_import
pub fn convert_to_bytes() {
// Convert any unsigned integeger to `Bytes`
// ANCHOR: to_bytes
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// ANCHOR_END: to_bytes
}
pub fn convert_from_bytes() {
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// Convert `Bytes` to an unsigned integeger
// ANCHOR: from_bytes
let u16_from_le_bytes: u16 = u16::from_le_bytes(little_endian_bytes);
let u16_from_be_bytes: u16 = u16::from_be_bytes(big_endian_bytes);
let u32_from_le_bytes: u32 = u32::from_le_bytes(little_endian_bytes);
let u32_from_be_bytes: u32 = u32::from_be_bytes(big_endian_bytes);
let u64_from_le_bytes: u64 = u64::from_le_bytes(little_endian_bytes);
let u64_from_be_bytes: u64 = u64::from_be_bytes(big_endian_bytes);
let u256_from_le_bytes = u256::from_le_bytes(little_endian_bytes);
let u256_from_be_bytes = u256::from_be_bytes(big_endian_bytes);
let b256_from_le_bytes = b256::from_le_bytes(little_endian_bytes);
let b256_from_be_bytes = b256::from_be_bytes(big_endian_bytes);
// ANCHOR_END: from_bytes
}
library;
// ANCHOR: to_bytes_import
use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}};
// ANCHOR_END: to_bytes_import
pub fn convert_to_bytes() {
// Convert any unsigned integeger to `Bytes`
// ANCHOR: to_bytes
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// ANCHOR_END: to_bytes
}
pub fn convert_from_bytes() {
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// Convert `Bytes` to an unsigned integeger
// ANCHOR: from_bytes
let u16_from_le_bytes: u16 = u16::from_le_bytes(little_endian_bytes);
let u16_from_be_bytes: u16 = u16::from_be_bytes(big_endian_bytes);
let u32_from_le_bytes: u32 = u32::from_le_bytes(little_endian_bytes);
let u32_from_be_bytes: u32 = u32::from_be_bytes(big_endian_bytes);
let u64_from_le_bytes: u64 = u64::from_le_bytes(little_endian_bytes);
let u64_from_be_bytes: u64 = u64::from_be_bytes(big_endian_bytes);
let u256_from_le_bytes = u256::from_le_bytes(little_endian_bytes);
let u256_from_be_bytes = u256::from_be_bytes(big_endian_bytes);
let b256_from_le_bytes = b256::from_le_bytes(little_endian_bytes);
let b256_from_be_bytes = b256::from_be_bytes(big_endian_bytes);
// ANCHOR_END: from_bytes
}
Convert from Bytes
library;
// ANCHOR: to_bytes_import
use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}};
// ANCHOR_END: to_bytes_import
pub fn convert_to_bytes() {
// Convert any unsigned integeger to `Bytes`
// ANCHOR: to_bytes
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// ANCHOR_END: to_bytes
}
pub fn convert_from_bytes() {
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// Convert `Bytes` to an unsigned integeger
// ANCHOR: from_bytes
let u16_from_le_bytes: u16 = u16::from_le_bytes(little_endian_bytes);
let u16_from_be_bytes: u16 = u16::from_be_bytes(big_endian_bytes);
let u32_from_le_bytes: u32 = u32::from_le_bytes(little_endian_bytes);
let u32_from_be_bytes: u32 = u32::from_be_bytes(big_endian_bytes);
let u64_from_le_bytes: u64 = u64::from_le_bytes(little_endian_bytes);
let u64_from_be_bytes: u64 = u64::from_be_bytes(big_endian_bytes);
let u256_from_le_bytes = u256::from_le_bytes(little_endian_bytes);
let u256_from_be_bytes = u256::from_be_bytes(big_endian_bytes);
let b256_from_le_bytes = b256::from_le_bytes(little_endian_bytes);
let b256_from_be_bytes = b256::from_be_bytes(big_endian_bytes);
// ANCHOR_END: from_bytes
}
library;
// ANCHOR: to_bytes_import
use std::{bytes::Bytes, bytes_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*}};
// ANCHOR_END: to_bytes_import
pub fn convert_to_bytes() {
// Convert any unsigned integeger to `Bytes`
// ANCHOR: to_bytes
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// ANCHOR_END: to_bytes
}
pub fn convert_from_bytes() {
let num = 5;
let little_endian_bytes: Bytes = num.to_le_bytes();
let big_endian_bytes: Bytes = num.to_be_bytes();
// Convert `Bytes` to an unsigned integeger
// ANCHOR: from_bytes
let u16_from_le_bytes: u16 = u16::from_le_bytes(little_endian_bytes);
let u16_from_be_bytes: u16 = u16::from_be_bytes(big_endian_bytes);
let u32_from_le_bytes: u32 = u32::from_le_bytes(little_endian_bytes);
let u32_from_be_bytes: u32 = u32::from_be_bytes(big_endian_bytes);
let u64_from_le_bytes: u64 = u64::from_le_bytes(little_endian_bytes);
let u64_from_be_bytes: u64 = u64::from_be_bytes(big_endian_bytes);
let u256_from_le_bytes = u256::from_le_bytes(little_endian_bytes);
let u256_from_be_bytes = u256::from_be_bytes(big_endian_bytes);
let b256_from_le_bytes = b256::from_le_bytes(little_endian_bytes);
let b256_from_be_bytes = b256::from_be_bytes(big_endian_bytes);
// ANCHOR_END: from_bytes
}
Byte Array Conversions
Convert to a Byte Array
library;
// ANCHOR: to_byte_array_import
use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*};
// ANCHOR_END: to_byte_array_import
pub fn to_byte_array() {
// ANCHOR: to_byte_array
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2u64;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let b256_1: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// little endian
let le_byte_array_from_u16: [u8; 2] = u16_1.to_le_bytes();
let le_byte_array_from_u32: [u8; 4] = u32_1.to_le_bytes();
let le_byte_array_from_u64: [u8; 8] = u64_1.to_le_bytes();
let le_byte_array_from_u256: [u8; 32] = u256_1.to_le_bytes();
let le_byte_array_from_b256: [u8; 32] = b256_1.to_le_bytes();
// big endian
let be_byte_array_from_u16: [u8; 2] = u16_1.to_be_bytes();
let be_byte_array_from_u32: [u8; 4] = u32_1.to_be_bytes();
let be_byte_array_from_u64: [u8; 8] = u64_1.to_be_bytes();
let be_byte_array_from_u256: [u8; 32] = u256_1.to_be_bytes();
let be_byte_array_from_b256: [u8; 32] = b256_1.to_be_bytes();
// ANCHOR_END: to_byte_array
}
pub fn from_byte_array() {
// ANCHOR: from_byte_array
let u16_byte_array: [u8; 2] = [2_u8, 1_u8];
let u32_byte_array: [u8; 4] = [4_u8, 3_u8, 2_u8, 1_u8];
let u64_byte_array: [u8; 8] = [8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8];
let u256_byte_array: [u8; 32] = [
32_u8, 31_u8, 30_u8, 29_u8, 28_u8, 27_u8, 26_u8, 25_u8, 24_u8, 23_u8, 22_u8,
21_u8, 20_u8, 19_u8, 18_u8, 17_u8, 16_u8, 15_u8, 14_u8, 13_u8, 12_u8, 11_u8,
10_u8, 9_u8, 8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8,
];
// little endian
let le_u16_from_byte_array: u16 = u16::from_le_bytes(u16_byte_array);
let le_u32_from_byte_array: u32 = u32::from_le_bytes(u32_byte_array);
let le_u64_from_byte_array: u64 = u64::from_le_bytes(u64_byte_array);
let le_u256_from_byte_array: u256 = u256::from_le_bytes(u256_byte_array);
let le_b256_from_byte_array: b256 = b256::from_le_bytes(u256_byte_array);
// big endian
let be_u16_from_byte_array: u16 = u16::from_be_bytes(u16_byte_array);
let be_u32_from_byte_array: u32 = u32::from_be_bytes(u32_byte_array);
let be_u64_from_byte_array: u64 = u64::from_be_bytes(u64_byte_array);
let be_u256_from_byte_array: u256 = u256::from_be_bytes(u256_byte_array);
let be_b256_from_byte_array: b256 = b256::from_be_bytes(u256_byte_array);
// ANCHOR_END: from_byte_array
}
library;
// ANCHOR: to_byte_array_import
use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*};
// ANCHOR_END: to_byte_array_import
pub fn to_byte_array() {
// ANCHOR: to_byte_array
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2u64;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let b256_1: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// little endian
let le_byte_array_from_u16: [u8; 2] = u16_1.to_le_bytes();
let le_byte_array_from_u32: [u8; 4] = u32_1.to_le_bytes();
let le_byte_array_from_u64: [u8; 8] = u64_1.to_le_bytes();
let le_byte_array_from_u256: [u8; 32] = u256_1.to_le_bytes();
let le_byte_array_from_b256: [u8; 32] = b256_1.to_le_bytes();
// big endian
let be_byte_array_from_u16: [u8; 2] = u16_1.to_be_bytes();
let be_byte_array_from_u32: [u8; 4] = u32_1.to_be_bytes();
let be_byte_array_from_u64: [u8; 8] = u64_1.to_be_bytes();
let be_byte_array_from_u256: [u8; 32] = u256_1.to_be_bytes();
let be_byte_array_from_b256: [u8; 32] = b256_1.to_be_bytes();
// ANCHOR_END: to_byte_array
}
pub fn from_byte_array() {
// ANCHOR: from_byte_array
let u16_byte_array: [u8; 2] = [2_u8, 1_u8];
let u32_byte_array: [u8; 4] = [4_u8, 3_u8, 2_u8, 1_u8];
let u64_byte_array: [u8; 8] = [8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8];
let u256_byte_array: [u8; 32] = [
32_u8, 31_u8, 30_u8, 29_u8, 28_u8, 27_u8, 26_u8, 25_u8, 24_u8, 23_u8, 22_u8,
21_u8, 20_u8, 19_u8, 18_u8, 17_u8, 16_u8, 15_u8, 14_u8, 13_u8, 12_u8, 11_u8,
10_u8, 9_u8, 8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8,
];
// little endian
let le_u16_from_byte_array: u16 = u16::from_le_bytes(u16_byte_array);
let le_u32_from_byte_array: u32 = u32::from_le_bytes(u32_byte_array);
let le_u64_from_byte_array: u64 = u64::from_le_bytes(u64_byte_array);
let le_u256_from_byte_array: u256 = u256::from_le_bytes(u256_byte_array);
let le_b256_from_byte_array: b256 = b256::from_le_bytes(u256_byte_array);
// big endian
let be_u16_from_byte_array: u16 = u16::from_be_bytes(u16_byte_array);
let be_u32_from_byte_array: u32 = u32::from_be_bytes(u32_byte_array);
let be_u64_from_byte_array: u64 = u64::from_be_bytes(u64_byte_array);
let be_u256_from_byte_array: u256 = u256::from_be_bytes(u256_byte_array);
let be_b256_from_byte_array: b256 = b256::from_be_bytes(u256_byte_array);
// ANCHOR_END: from_byte_array
}
Convert from a Byte Array
library;
// ANCHOR: to_byte_array_import
use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*};
// ANCHOR_END: to_byte_array_import
pub fn to_byte_array() {
// ANCHOR: to_byte_array
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2u64;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let b256_1: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// little endian
let le_byte_array_from_u16: [u8; 2] = u16_1.to_le_bytes();
let le_byte_array_from_u32: [u8; 4] = u32_1.to_le_bytes();
let le_byte_array_from_u64: [u8; 8] = u64_1.to_le_bytes();
let le_byte_array_from_u256: [u8; 32] = u256_1.to_le_bytes();
let le_byte_array_from_b256: [u8; 32] = b256_1.to_le_bytes();
// big endian
let be_byte_array_from_u16: [u8; 2] = u16_1.to_be_bytes();
let be_byte_array_from_u32: [u8; 4] = u32_1.to_be_bytes();
let be_byte_array_from_u64: [u8; 8] = u64_1.to_be_bytes();
let be_byte_array_from_u256: [u8; 32] = u256_1.to_be_bytes();
let be_byte_array_from_b256: [u8; 32] = b256_1.to_be_bytes();
// ANCHOR_END: to_byte_array
}
pub fn from_byte_array() {
// ANCHOR: from_byte_array
let u16_byte_array: [u8; 2] = [2_u8, 1_u8];
let u32_byte_array: [u8; 4] = [4_u8, 3_u8, 2_u8, 1_u8];
let u64_byte_array: [u8; 8] = [8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8];
let u256_byte_array: [u8; 32] = [
32_u8, 31_u8, 30_u8, 29_u8, 28_u8, 27_u8, 26_u8, 25_u8, 24_u8, 23_u8, 22_u8,
21_u8, 20_u8, 19_u8, 18_u8, 17_u8, 16_u8, 15_u8, 14_u8, 13_u8, 12_u8, 11_u8,
10_u8, 9_u8, 8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8,
];
// little endian
let le_u16_from_byte_array: u16 = u16::from_le_bytes(u16_byte_array);
let le_u32_from_byte_array: u32 = u32::from_le_bytes(u32_byte_array);
let le_u64_from_byte_array: u64 = u64::from_le_bytes(u64_byte_array);
let le_u256_from_byte_array: u256 = u256::from_le_bytes(u256_byte_array);
let le_b256_from_byte_array: b256 = b256::from_le_bytes(u256_byte_array);
// big endian
let be_u16_from_byte_array: u16 = u16::from_be_bytes(u16_byte_array);
let be_u32_from_byte_array: u32 = u32::from_be_bytes(u32_byte_array);
let be_u64_from_byte_array: u64 = u64::from_be_bytes(u64_byte_array);
let be_u256_from_byte_array: u256 = u256::from_be_bytes(u256_byte_array);
let be_b256_from_byte_array: b256 = b256::from_be_bytes(u256_byte_array);
// ANCHOR_END: from_byte_array
}
library;
// ANCHOR: to_byte_array_import
use std::array_conversions::{b256::*, u16::*, u256::*, u32::*, u64::*};
// ANCHOR_END: to_byte_array_import
pub fn to_byte_array() {
// ANCHOR: to_byte_array
let u16_1: u16 = 2u16;
let u32_1: u32 = 2u32;
let u64_1: u64 = 2u64;
let u256_1: u256 = 0x0000000000000000000000000000000000000000000000000000000000000002u256;
let b256_1: b256 = 0x000000000000000000000000000000000000000000000000000000000000002A;
// little endian
let le_byte_array_from_u16: [u8; 2] = u16_1.to_le_bytes();
let le_byte_array_from_u32: [u8; 4] = u32_1.to_le_bytes();
let le_byte_array_from_u64: [u8; 8] = u64_1.to_le_bytes();
let le_byte_array_from_u256: [u8; 32] = u256_1.to_le_bytes();
let le_byte_array_from_b256: [u8; 32] = b256_1.to_le_bytes();
// big endian
let be_byte_array_from_u16: [u8; 2] = u16_1.to_be_bytes();
let be_byte_array_from_u32: [u8; 4] = u32_1.to_be_bytes();
let be_byte_array_from_u64: [u8; 8] = u64_1.to_be_bytes();
let be_byte_array_from_u256: [u8; 32] = u256_1.to_be_bytes();
let be_byte_array_from_b256: [u8; 32] = b256_1.to_be_bytes();
// ANCHOR_END: to_byte_array
}
pub fn from_byte_array() {
// ANCHOR: from_byte_array
let u16_byte_array: [u8; 2] = [2_u8, 1_u8];
let u32_byte_array: [u8; 4] = [4_u8, 3_u8, 2_u8, 1_u8];
let u64_byte_array: [u8; 8] = [8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8];
let u256_byte_array: [u8; 32] = [
32_u8, 31_u8, 30_u8, 29_u8, 28_u8, 27_u8, 26_u8, 25_u8, 24_u8, 23_u8, 22_u8,
21_u8, 20_u8, 19_u8, 18_u8, 17_u8, 16_u8, 15_u8, 14_u8, 13_u8, 12_u8, 11_u8,
10_u8, 9_u8, 8_u8, 7_u8, 6_u8, 5_u8, 4_u8, 3_u8, 2_u8, 1_u8,
];
// little endian
let le_u16_from_byte_array: u16 = u16::from_le_bytes(u16_byte_array);
let le_u32_from_byte_array: u32 = u32::from_le_bytes(u32_byte_array);
let le_u64_from_byte_array: u64 = u64::from_le_bytes(u64_byte_array);
let le_u256_from_byte_array: u256 = u256::from_le_bytes(u256_byte_array);
let le_b256_from_byte_array: b256 = b256::from_le_bytes(u256_byte_array);
// big endian
let be_u16_from_byte_array: u16 = u16::from_be_bytes(u16_byte_array);
let be_u32_from_byte_array: u32 = u32::from_be_bytes(u32_byte_array);
let be_u64_from_byte_array: u64 = u64::from_be_bytes(u64_byte_array);
let be_u256_from_byte_array: u256 = u256::from_be_bytes(u256_byte_array);
let be_b256_from_byte_array: b256 = b256::from_be_bytes(u256_byte_array);
// ANCHOR_END: from_byte_array
}
Functions
Functions in Sway are declared with the fn keyword. Let's take a look:
fn equals(first_param: u64, second_param: u64) -> bool {
first_param == second_param
}
We have just declared a function named equals which takes two parameters: first_param and second_param. The parameters must both be 64-bit unsigned integers.
This function also returns a bool value, i.e. either true or false. This function returns true if the two given parameters are equal, and false if they are not. If we want to use this function, we can do so like this:
fn main() {
equals(5, 5); // evaluates to `true`
equals(5, 6); // evaluates to `false`
}
Mutable Parameters
We can make a function parameter mutable by adding ref mut before the parameter name. This allows mutating the argument passed into the function when the function is called.
For example:
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
This function is allowed to mutate its parameter num because of the mut keyword. In addition, the ref keyword instructs the function to modify the argument passed to it when the function is called, instead of modifying a local copy of it.
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
Note that the variable num itself has to be declared as mutable for the above to compile.
Note It is not currently allowed to use
mutwithoutrefor vice versa for a function parameter.
Similarly, ref mut can be used with more complex data types such as:
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
We can then call these functions as shown below:
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
Note The only place, in a Sway program, where the
refkeyword is valid is before a mutable function parameter.
Structs, Tuples, and Enums
Structs
Structs in Sway are a named grouping of types. You may also be familiar with structs via another name: product types. Sway does not make any significantly unique usages of structs; they are similar to most other languages which have structs. If you're coming from an object-oriented background, a struct is like the data attributes of an object.
Those data attributes are called fields and can be either public or private.
Private struct fields can be accessed only within the module in which their struct is declared. Public fields are accessible everywhere where the struct is accessible. This access control on the field level allows more fine grained encapsulation of data.
To explain these concepts, let's take a look at the following example, in which we have a module called data_structures.
In that module, we declare a struct named Foo with two fields. The first field is named bar, it is public and it accepts values of type u64. The second field is named baz, it is also public and it accepts bool values.
In a similar way, we define the structs Point, Line, and TupleInStruct. Since all those structs are public, and all their fields are public, they can be instantiated in other modules using the struct instantiation syntax as shown below.
On the other hand, the struct StructWithPrivateFields can be instantiated only within the data_structures module, because it contains private fields. To be able to create instances of such structs outside of the module in which they are declared, the struct must offer constructor associated functions.
// the _data_structures_ module
library;
// Declare a struct type
pub struct Foo {
pub bar: u64,
pub baz: bool,
}
// Struct types for destructuring
pub struct Point {
pub x: u64,
pub y: u64,
}
pub struct Line {
pub p1: Point,
pub p2: Point,
}
pub struct TupleInStruct {
pub nested_tuple: (u64, (u32, (bool, str))),
}
// Struct type instantiable only in the module _data_structures_
pub struct StructWithPrivateFields {
pub public_field: u64,
private_field: u64,
other_private_field: u64,
}
In order to instantiate the struct we use struct instantiation syntax, which is very similar to the declaration syntax except with expressions in place of types.
There are three ways to instantiate the struct.
- Hard coding values for the fields
- Passing in variables with names different than the struct fields
- Using a shorthand notation via variables that are the same as the field names
library;
mod data_structures;
use data_structures::{Foo, Line, Point, TupleInStruct};
fn hardcoded_instantiation() -> Foo {
// Instantiate `foo` as `Foo`
let mut foo = Foo {
bar: 42,
baz: false,
};
// Access and write to "baz"
foo.baz = true;
// Return the struct
foo
}
fn variable_instantiation() -> Foo {
// Declare variables with the same names as the fields in `Foo`
let number = 42;
let truthness = false;
// Instantiate `foo` as `Foo`
let mut foo = Foo {
bar: number,
baz: truthness,
};
// Access and write to "baz"
foo.baz = true;
// Return the struct
foo
}
fn shorthand_instantiation() -> Foo {
// Declare variables with the same names as the fields in `Foo`
let bar = 42;
let baz = false;
// Instantiate `foo` as `Foo`
let mut foo = Foo { bar, baz };
// Access and write to "baz"
foo.baz = true;
// Return the struct
foo
}
fn struct_destructuring() {
let point1 = Point { x: 0, y: 0 };
// Destructure the values from the struct into variables
let Point { x, y } = point1;
let point2 = Point { x: 1, y: 1 };
// If you do not care about specific struct fields then use ".." at the end of your variable list
let Point { x, .. } = point2;
let line = Line {
p1: point1,
p2: point2,
};
// Destructure the values from the nested structs into variables
let Line {
p1: Point { x: x0, y: y0 },
p2: Point { x: x1, y: y1 },
} = line;
// You may also destructure tuples nested in structs and structs nested in tuples
let tuple_in_struct = TupleInStruct {
nested_tuple: (42u64, (42u32, (true, "ok"))),
};
let TupleInStruct {
nested_tuple: (a, (b, (c, d))),
} = tuple_in_struct;
let struct_in_tuple = (Point { x: 2, y: 4 }, Point { x: 3, y: 6 });
let (Point { x: x0, y: y0 }, Point { x: x1, y: y1 }) = struct_in_tuple;
}
Note You can mix and match all 3 ways to instantiate the struct at the same time. Moreover, the order of the fields does not matter when instantiating however we encourage declaring the fields in alphabetical order and instantiating them in the same alphabetical order
Furthermore, multiple variables can be extracted from a struct using the destructuring syntax.
Struct Memory Layout
Note This information is not vital if you are new to the language, or programming in general
Structs have zero memory overhead. What that means is that in memory, each struct field is laid out sequentially. No metadata regarding the struct's name or other properties is preserved at runtime. In other words, structs are compile-time constructs. This is the same in Rust, but different in other languages with runtimes like Java.
Tuples
Tuples are a basic static-length type which contain multiple different types within themselves. The type of a tuple is defined by the types of the values within it, and a tuple can contain basic types as well as structs and enums.
You can access values directly by using the . syntax. Moreover, multiple variables can be extracted from a tuple using the destructuring syntax.
library;
fn tuple() {
// You can declare the types yourself
let tuple1: (u8, bool, u64) = (100, false, 10000);
// Or have the types be inferred
let mut tuple2 = (5, true, ("Sway", 8));
// Retrieve values from tuples
let number = tuple1.0;
let sway = tuple2.2.1;
// Destructure the values from the tuple into variables
let (n1, truthness, n2) = tuple1;
// If you do not care about specific values then use "_"
let (_, truthness, _) = tuple2;
// Internally mutate the tuple
tuple2.1 = false;
// Or change the values all at once (must keep the same data types)
tuple2 = (9, false, ("Fuel", 99));
}
Enums
Enumerations, or enums, are also known as sum types. An enum is a type that could be one of several variants. To declare an enum, you enumerate all potential variants.
Here, we have defined five potential colors. Each enum variant is just the color name. As there is no extra data associated with each variant, we say that each variant is of type (), or unit.
library;
// Declare the enum
enum Color {
Blue: (),
Green: (),
Red: (),
Silver: (),
Grey: (),
}
fn main() {
// To instantiate a variable with the value of an enum the syntax is
let blue = Color::Blue;
let silver = Color::Silver;
}
Enums of Structs
It is also possible to have an enum variant contain extra data. Take a look at this more substantial example, which combines struct declarations with enum variants:
library;
struct Item {
price: u64,
amount: u64,
id: u64,
}
enum MyEnum {
Item: Item,
}
fn main() {
let my_enum = MyEnum::Item(Item {
price: 5,
amount: 2,
id: 42,
});
}
Enums of Enums
It is possible to define enums of enums:
library;
pub enum Error {
StateError: StateError,
UserError: UserError,
}
pub enum StateError {
Void: (),
Pending: (),
Completed: (),
}
pub enum UserError {
InsufficientPermissions: (),
Unauthorized: (),
}
Preferred usage
The preferred way to use enums is to use the individual (not nested) enums directly because they are easy to follow and the lines are short:
library;
use ::enum_of_enums::{StateError, UserError};
fn preferred() {
let error1 = StateError::Void;
let error2 = UserError::Unauthorized;
}
Inadvisable
If you wish to use the nested form of enums via the Error enum from the example above, then you can instantiate them into variables using the following syntax:
library;
use ::enum_of_enums::{Error, StateError, UserError};
fn avoid() {
let error1 = Error::StateError(StateError::Void);
let error2 = Error::UserError(UserError::Unauthorized);
}
Key points to note:
- You must import all of the enums you need instead of just the
Errorenum - The lines may get unnecessarily long (depending on the names)
- The syntax is not the most ergonomic
Enum Memory Layout
Note This information is not vital if you are new to the language, or programming in general.
Enums do have some memory overhead. To know which variant is being represented, Sway stores a one-word (8-byte) tag for the enum variant. The space reserved after the tag is equivalent to the size of the largest enum variant. So, to calculate the size of an enum in memory, add 8 bytes to the size of the largest variant. For example, in the case of Color above, where the variants are all (), the size would be 8 bytes since the size of the largest variant is 0 bytes.
Methods and Associated Functions
Methods
Methods are similar to functions in that we declare them with the fn keyword and they have parameters and return a value. However, unlike functions, Methods are defined within the context of a struct (or enum), and either refers to that type or mutates it. The first parameter of a method is always self, which represents the instance of the struct (or enum) the method is being called on.
Associated Functions
Associated functions are very similar to methods, in that they are also defined in the context of a struct or enum, but they do not actually use any of the data in the struct and as a result do not take self as a parameter. Associated functions could be standalone functions, but they are included in a specific type for organizational or semantic reasons.
Constructors
Constructors are associated functions that construct, or in other words instantiate, new instances of a type. Their return type is always the type itself. E.g., public structs that have private fields must provide a public constructor, or otherwise they cannot be instantiated outside of the module in which they are declared.
Declaring Methods and Associated Functions
To declare methods and associated functions for a struct or enum, use an impl block. Here, impl is short for implementation.
script;
struct Foo {
bar: u64,
baz: bool,
}
impl Foo {
// this is a _method_, as it takes `self` as a parameter.
fn is_baz_true(self) -> bool {
self.baz
}
// this is an _associated function_, since it does not take `self` as a parameter.
// it is at the same time a _constructor_ because it instantiates and returns
// a new instance of `Foo`.
fn new_foo(number: u64, boolean: bool) -> Foo {
Foo {
bar: number,
baz: boolean,
}
}
}
fn main() {
let foo = Foo::new_foo(42, true);
assert(foo.is_baz_true());
}
To call a method, simply use dot syntax: foo.iz_baz_true().
Similarly to free functions, methods and associated functions may accept ref mut parameters.
For example:
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
and when called:
script;
enum Color {
Red: (),
Blue: (),
}
// ANCHOR: increment
fn increment(ref mut num: u32) {
let prev = num;
num = prev + 1u32;
}
// ANCHOR_END: increment
// ANCHOR: tuple_and_enum
fn swap_tuple(ref mut pair: (u64, u64)) {
let temp = pair.0;
pair.0 = pair.1;
pair.1 = temp;
}
fn update_color(ref mut color: Color, new_color: Color) {
color = new_color;
}
// ANCHOR_END: tuple_and_enum
// ANCHOR: move_right
struct Coordinates {
x: u64,
y: u64,
}
impl Coordinates {
fn move_right(ref mut self, distance: u64) {
self.x += distance;
}
}
// ANCHOR_END: move_right
fn main() {
// ANCHOR: call_increment
let mut num: u32 = 0;
increment(num);
assert(num == 1u32); // The function `increment()` modifies `num`
// ANCHOR_END: call_increment
// ANCHOR: call_tuple_and_enum
let mut tuple = (42, 24);
swap_tuple(tuple);
assert(tuple.0 == 24); // The function `swap_tuple()` modifies `tuple.0`
assert(tuple.1 == 42); // The function `swap_tuple()` modifies `tuple.1`
let mut color = Color::Red;
update_color(color, Color::Blue);
assert(match color {
Color::Blue => true,
_ => false,
}); // The function `update_color()` modifies the color to Blue
// ANCHOR_END: call_tuple_and_enum
// ANCHOR: call_move_right
let mut point = Coordinates { x: 1, y: 1 };
point.move_right(5);
assert(point.x == 6);
assert(point.y == 1);
// ANCHOR_END: call_move_right
}
Constants
Constants are similar to variables; however, there are a few differences:
- Constants are always evaluated at compile-time.
- Constants can be declared both inside of a function and at global /
implscope. - The
mutkeyword cannot be used with constants.
const ID: u32 = 0;
Constant initializer expressions can be quite complex, but they cannot use, for
instance, assembly instructions, storage access, mutable variables, loops and
return statements. Although, function calls, primitive types and compound data
structures are perfectly fine to use:
fn bool_to_num(b: bool) -> u64 {
if b {
1
} else {
0
}
}
fn arr_wrapper(a: u64, b: u64, c: u64) -> [u64; 3] {
[a, b, c]
}
const ARR2 = arr_wrapper(bool_to_num(1) + 42, 2, 3);
Associated Constants
Associated constants are constants associated with a type and can be declared in an impl block or in a trait definition.
Associated constants declared inside a trait definition may omit their initializers to indicate that each implementation of the trait must specify those initializers.
The identifier is the name of the constant used in the path. The type is the type that the definition has to implement.
You can define an associated const directly in the interface surface of a trait:
script;
trait ConstantId {
const ID: u32 = 0;
}
Alternatively, you can also declare it in the trait, and implement it in the interface of the types implementing the trait.
script;
trait ConstantId {
const ID: u32;
}
struct Struct {}
impl ConstantId for Struct {
const ID: u32 = 1;
}
fn main() -> u32 {
Struct::ID
}
impl self Constants
Constants can also be declared inside non-trait impl blocks.
script;
struct Point {
x: u64,
y: u64,
}
impl Point {
const ZERO: Point = Point { x: 0, y: 0 };
}
fn main() -> u64 {
Point::ZERO.x
}
Configurable Constants
Configurable constants are special constants that behave like regular constants in the sense that they cannot change during program execution, but they can be configured after the Sway program has been built. The Rust and TS SDKs allow updating the values of these constants by injecting new values for them directly in the bytecode without having to build the program again. These are useful for contract factories and behave somewhat similarly to immutable variables from languages like Solidity.
Configurable constants are declared inside a configurable block and require a type ascription and an initializer as follows:
contract;
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
// ANCHOR: configurable_block
configurable {
U8: u8 = 8u8,
BOOL: bool = true,
ARRAY: [u32; 3] = [253u32, 254u32, 255u32],
STR_4: str[4] = __to_str_array("fuel"),
STRUCT: StructWithGeneric<u8> = StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}
// ANCHOR_END: configurable_block
abi TestContract {
fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructWithGeneric<u8>);
}
impl TestContract for Contract {
// ANCHOR: using_configurables
fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructWithGeneric<u8>) {
(U8, BOOL, ARRAY, STR_4, STRUCT)
}
// ANCHOR_END: using_configurables
}
At most one configurable block is allowed in a Sway project. Moreover, configurable blocks are not allowed in libraries.
Configurable constants can be read directly just like regular constants:
contract;
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
// ANCHOR: configurable_block
configurable {
U8: u8 = 8u8,
BOOL: bool = true,
ARRAY: [u32; 3] = [253u32, 254u32, 255u32],
STR_4: str[4] = __to_str_array("fuel"),
STRUCT: StructWithGeneric<u8> = StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}
// ANCHOR_END: configurable_block
abi TestContract {
fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructWithGeneric<u8>);
}
impl TestContract for Contract {
// ANCHOR: using_configurables
fn return_configurables() -> (u8, bool, [u32; 3], str[4], StructWithGeneric<u8>) {
(U8, BOOL, ARRAY, STR_4, STRUCT)
}
// ANCHOR_END: using_configurables
}
Comments and Logging
Comments
Comments in Sway start with two slashes and continue until the end of the line. For comments that extend beyond a single line, you'll need to include // on each line.
// hello world
// let's make a couple of lines
// commented.
You can also place comments at the ends of lines containing code.
fn main() {
let baz = 8; // Eight is a lucky number
}
You can also do block comments
fn main() {
/*
You can write on multiple lines
like this if you want
*/
let baz = 8;
}
Logging
The logging library provides a generic log function that can be imported using use std::logging::log and used to log variables of any type. Each call to log appends a receipt to the list of receipts. There are two types of receipts that a log can generate: Log and LogData.
fn log_values(){
// Generates a Log receipt
log(42);
// Generates a LogData receipt
let string = "sway";
log(string);
}
Log Receipt
The Log receipt is generated for non-reference types, namely bool, u8, u16, u32, and u64.
For example, logging an integer variable x that holds the value 42 using log(x) may generate the following receipt:
"Log": {
"id": "0000000000000000000000000000000000000000000000000000000000000000",
"is": 10352,
"pc": 10404,
"ra": 42,
"rb": 1018205,
"rc": 0,
"rd": 0
}
Note that ra will include the value being logged. The additional registers rc and rd will be zero when using log while rb may include a non-zero value representing a unique ID for the log instance. The unique ID is not meaningful on its own but allows the Rust and the TS SDKs to know the type of the data being logged, by looking up the log ID in the JSON ABI file.
LogData Receipt
LogData is generated for reference types which include all types except for non_reference types; and for non-reference types bigger than 64-bit integers, for example, u256;
For example, logging a b256 variable b that holds the value 0x1111111111111111111111111111111111111111111111111111111111111111 using log(b) may generate the following receipt:
"LogData": {
"data": "1111111111111111111111111111111111111111111111111111111111111111",
"digest": "02d449a31fbb267c8f352e9968a79e3e5fc95c1bbeaa502fd6454ebde5a4bedc",
"id": "0000000000000000000000000000000000000000000000000000000000000000",
"is": 10352,
"len": 32,
"pc": 10444,
"ptr": 10468,
"ra": 0,
"rb": 1018194
}
Note that data in the receipt above will include the value being logged as a hexadecimal. Similarly to the Log receipt, additional registers are written: ra will always be zero when using log, while rb will contain a unique ID for the log instance.
Note The Rust SDK exposes APIs that allow you to retrieve the logged values and display them nicely based on their types as indicated in the JSON ABI file.
Control Flow
if expressions
Sway supports if, else, and else if expressions that allow you to branch your code depending on conditions.
For example:
fn main() {
let number = 6;
if number % 4 == 0 {
// do something
} else if number % 3 == 0 {
// do something else
} else {
// do something else
}
}
Using if in a let statement
Like Rust, ifs are expressions in Sway. What this means is you can use if expressions on the right side of a let statement to assign the outcome to a variable.
let my_data = if some_bool < 10 { foo() } else { bar() };
Note that all branches of the if expression must return a value of the same type.
match expressions
Sway supports advanced pattern matching through exhaustive match expressions. Unlike an if expression, a match expression asserts at compile time that all possible patterns have been matched. If you don't handle all the patterns, you will get compiler error indicating that your match expression is non-exhaustive.
The basic syntax of a match expression is as follows:
let result = match expression {
pattern1 => code_to_execute_if_expression_matches_pattern1,
pattern2 => code_to_execute_if_expression_matches_pattern2,
pattern3 | pattern4 => code_to_execute_if_expression_matches_pattern3_or_pattern4
...
_ => code_to_execute_if_expression_matches_no_pattern,
}
Some examples of how you can use a match expression:
script;
// helper functions for our example
fn on_even(num: u64) {
// do something with even numbers
}
fn on_odd(num: u64) {
// do something with odd numbers
}
fn main(num: u64) -> u64 {
// Match as an expression
let is_even = match num % 2 {
0 => true,
_ => false,
};
// Match as control flow
let x = 12;
match x {
5 => on_odd(x),
_ => on_even(x),
};
// Match an enum
enum Weather {
Sunny: (),
Rainy: (),
Cloudy: (),
Snowy: (),
}
let current_weather = Weather::Sunny;
let avg_temp = match current_weather {
Weather::Sunny => 80,
Weather::Rainy => 50,
Weather::Cloudy => 60,
Weather::Snowy => 20,
};
let is_sunny = match current_weather {
Weather::Sunny => true,
Weather::Rainy | Weather::Cloudy | Weather::Snowy => false,
};
// match expression used for a return
let outside_temp = Weather::Sunny;
match outside_temp {
Weather::Sunny => 80,
Weather::Rainy => 50,
Weather::Cloudy => 60,
Weather::Snowy => 20,
}
}
Loops
while
This is what a while loop looks like:
while counter < 10 {
counter = counter + 1;
}
You need the while keyword, some condition (value < 10 in this case) which will be evaluated each iteration, and a block of code inside the curly braces ({...}) to execute each iteration.
for
This is what a for loop that computes the sum of a vector of numbers looks like:
for element in vector.iter() {
sum += element;
}
You need the for keyword, some pattern that contains variable names such as element in this case, the ìn keyword followed by an iterator, and a block of code inside the curly braces ({...}) to execute each iteration. vector.iter() in the example above returns an iterator for the vector. In each iteration, the value of element is updated with the next value in the iterator until the end of the vector is reached and the for loop iteration ends.
Modifying the vector during iteration, by e.g. adding or removing elements, is a logical error and results in an undefined behavior:
// The behavior of this `for` loop is undefined because
// the `vector` gets modified within the loop.
for element in vector.iter() {
if element == 3 {
vector.push(6); // Modification of the vector!
}
}
break and continue
break and continue keywords are available to use inside the body of a while or for loop. The purpose of the break statement is to break out of a loop early:
script;
// ANCHOR: break_example
fn break_example() -> u64 {
let mut counter = 1;
let mut sum = 0;
let num = 10;
while true {
if counter > num {
break;
}
sum += counter;
counter += 1;
}
sum // 1 + 2 + .. + 10 = 55
}
// ANCHOR_END: break_example
// ANCHOR: continue_example
fn continue_example() -> u64 {
let mut counter = 0;
let mut sum = 0;
let num = 10;
while counter < num {
counter += 1;
if counter % 2 == 0 {
continue;
}
sum += counter;
}
sum // 1 + 3 + .. + 9 = 25
}
// ANCHOR_END: continue_example
fn main() -> u64 {
break_example() + continue_example() // 55 + 25 = 80
}
The purpose of the continue statement is to skip a portion of a loop in an iteration and jump directly into the next iteration:
script;
// ANCHOR: break_example
fn break_example() -> u64 {
let mut counter = 1;
let mut sum = 0;
let num = 10;
while true {
if counter > num {
break;
}
sum += counter;
counter += 1;
}
sum // 1 + 2 + .. + 10 = 55
}
// ANCHOR_END: break_example
// ANCHOR: continue_example
fn continue_example() -> u64 {
let mut counter = 0;
let mut sum = 0;
let num = 10;
while counter < num {
counter += 1;
if counter % 2 == 0 {
continue;
}
sum += counter;
}
sum // 1 + 3 + .. + 9 = 25
}
// ANCHOR_END: continue_example
fn main() -> u64 {
break_example() + continue_example() // 55 + 25 = 80
}
Nested loops
You can also use nested while loops if needed:
while condition_1 == true {
// do stuff...
while condition_2 == true {
// do more stuff...
}
}
Blockchain Development with Sway
Sway is fundamentally a blockchain language. Because of this, it has some features and requirements that you may not have seen in general-purpose programming languages.
These are also some concepts related to the FuelVM and Fuel ecosystem that you may utilize when writing Sway.
- Hashing and Cryptography
- Contract Storage
- Function Purity
- Identifiers
- Native Assets
- Access Control
- Calling Contracts
- External Code Execution
Hashing and Cryptography
The Sway standard library provides easy access to a selection of cryptographic hash functions (sha256 and EVM-compatible keccak256), and EVM-compatible secp256k1-based signature recovery operations.
Hashing
script;
use std::hash::*;
impl Hash for Location {
fn hash(self, ref mut state: Hasher) {
match self {
Location::Earth => {
0_u8.hash(state);
}
Location::Mars => {
1_u8.hash(state);
}
}
}
}
impl Hash for Stats {
fn hash(self, ref mut state: Hasher) {
self.strength.hash(state);
self.agility.hash(state);
}
}
impl Hash for Person {
fn hash(self, ref mut state: Hasher) {
self.name.hash(state);
self.age.hash(state);
self.alive.hash(state);
self.location.hash(state);
self.stats.hash(state);
self.some_tuple.hash(state);
self.some_array.hash(state);
self.some_b256.hash(state);
}
}
const VALUE_A = 0x9280359a3b96819889d30614068715d634ad0cf9bba70c0f430a8c201138f79f;
enum Location {
Earth: (),
Mars: (),
}
struct Person {
name: str,
age: u64,
alive: bool,
location: Location,
stats: Stats,
some_tuple: (bool, u64),
some_array: [u64; 2],
some_b256: b256,
}
struct Stats {
strength: u64,
agility: u64,
}
fn main() {
let zero = b256::min();
// Use the generic sha256 to hash some integers
let sha_hashed_u8 = sha256(u8::max());
let sha_hashed_u16 = sha256(u16::max());
let sha_hashed_u32 = sha256(u32::max());
let sha_hashed_u64 = sha256(u64::max());
// Or hash a b256
let sha_hashed_b256 = sha256(VALUE_A);
// You can hash booleans too
let sha_hashed_bool = sha256(true);
// Strings are not a problem either
let sha_hashed_str = sha256("Fastest Modular Execution Layer!");
// Tuples of any size work too
let sha_hashed_tuple = sha256((true, 7));
// As do arrays
let sha_hashed_array = sha256([4, 5, 6]);
// Enums work too
let sha_hashed_enum = sha256(Location::Earth);
// Complex structs are not a problem
let sha_hashed_struct = sha256(Person {
name: "John",
age: 9000,
alive: true,
location: Location::Mars,
stats: Stats {
strength: 10,
agility: 9,
},
some_tuple: (true, 8),
some_array: [17, 76],
some_b256: zero,
});
log(sha_hashed_u8);
log(sha_hashed_u16);
log(sha_hashed_u32);
log(sha_hashed_u64);
log(sha_hashed_b256);
log(sha_hashed_bool);
log(sha_hashed_str);
log(sha_hashed_tuple);
log(sha_hashed_array);
log(sha_hashed_enum);
log(sha_hashed_struct);
// Use the generic keccak256 to hash some integers
let keccak_hashed_u8 = keccak256(u8::max());
let keccak_hashed_u16 = keccak256(u16::max());
let keccak_hashed_u32 = keccak256(u32::max());
let keccak_hashed_u64 = keccak256(u64::max());
// Or hash a b256
let keccak_hashed_b256 = keccak256(VALUE_A);
// You can hash booleans too
let keccak_hashed_bool = keccak256(true);
// Strings are not a problem either
let keccak_hashed_str = keccak256("Fastest Modular Execution Layer!");
// Tuples of any size work too
let keccak_hashed_tuple = keccak256((true, 7));
// As do arrays
let keccak_hashed_array = keccak256([4, 5, 6]);
// Enums work too
let keccak_hashed_enum = keccak256(Location::Earth);
// Complex structs are not a problem
let keccak_hashed_struct = keccak256(Person {
name: "John",
age: 9000,
alive: true,
location: Location::Mars,
stats: Stats {
strength: 10,
agility: 9,
},
some_tuple: (true, 8),
some_array: [17, 76],
some_b256: zero,
});
log(keccak_hashed_u8);
log(keccak_hashed_u16);
log(keccak_hashed_u32);
log(keccak_hashed_u64);
log(keccak_hashed_b256);
log(keccak_hashed_bool);
log(keccak_hashed_str);
log(keccak_hashed_tuple);
log(keccak_hashed_array);
log(keccak_hashed_enum);
log(keccak_hashed_struct);
}
Cryptographic Signature Recovery and Verification
Fuel supports 3 asymmetric cryptographic signature schemes; Secp256k1, Secp256r1, and Ed25519.
Public Key Recovery
Given a Signature and a sign Message, you can recover a PublicKey.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
Signed Message Address Recovery
Given a Signature and signed Message, you can recover a Fuel Address.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
Signed Message EVM Address Recovery
Recovery of EVM addresses is also supported.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
Public Key Signature Verification
Given a Signature, PublicKey, and Message, you can verify that the message was signed using the public key.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
Address Signature Verification
Given a Signature, Address, and Message, you can verify that the message was signed by the address.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
EVM Address Signature Verification
Recovery of EVM addresses verification is also supported.
script;
fn main() {}
use std::{
crypto::{
ed25519::*,
message::*,
public_key::*,
secp256k1::*,
secp256r1::*,
signature::*,
},
hash::{
Hash,
sha256,
},
vm::evm::evm_address::EvmAddress,
};
fn public_key_recovery() {
// ANCHOR: public_key_recovery
// Secp256rk1 Public Key Recovery
let secp256k1_signature: Signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered public key pair.
let secp256k1_public_key = secp256k1_signature.recover(signed_message);
assert(secp256k1_public_key.is_ok());
assert(
secp256k1_public_key
.unwrap() == PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
)),
);
// Secp256r1 Public Key Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A recovered public key pair.
let secp256r1_public_key = secp256r1_signature.recover(signed_message);
assert(secp256r1_public_key.is_ok());
assert(
secp256r1_public_key
.unwrap() == PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
)),
);
// ANCHOR_END: public_key_recovery
}
fn address_recovery() {
// ANCHOR: address_recovery
// Secp256k1 Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A recovered Fuel address.
let secp256k1_address = secp256k1_signature.address(signed_message);
assert(secp256k1_address.is_ok());
assert(
secp256k1_address
.unwrap() == Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881),
);
// Secp256r1 Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered Fuel address.
let secp256r1_address = secp256r1_signature.address(signed_message);
assert(secp256r1_address.is_ok());
assert(
secp256r1_address
.unwrap() == Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a),
);
// ANCHOR_END: address_recovery
}
fn evm_address_recovery() {
// ANCHOR: evm_address_recovery
// Secp256k1 EVM Address Recovery
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_evm_address = secp256k1_signature.evm_address(signed_message);
assert(secp256k1_evm_address.is_ok());
assert(
secp256k1_evm_address
.unwrap() == EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb),
);
// Secp256r1 EVM Address Recovery
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_evm_address = secp256r1_signature.evm_address(signed_message);
assert(secp256r1_evm_address.is_ok());
assert(
secp256r1_evm_address
.unwrap() == EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a),
);
// ANCHOR_END: evm_address_recovery
}
fn signature_verification() {
// ANCHOR: signature_verification
// Secp256k1 Signature Verification
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
)));
let secp256k1_public_key = PublicKey::from((
0x41a55558a3486b6ee3878f55f16879c0798afd772c1506de44aba90d29b6e65c,
0x341ca2e0a3d5827e78d838e35b29bebe2a39ac30b58999e1138c9467bf859965,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified public key
let secp256k1_verified = secp256k1_signature.verify(secp256k1_public_key, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Signature Verification
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876712afadbff382e1bf31c44437823ed761cc3600d0016de511ac,
0x44ac566bd156b4fc71a4a4cb2655d3da360c695edb27dc3b64d621e122fea23d,
)));
let secp256r1_public_key = PublicKey::from((
0xd6ea577a54ae42411fbc78d686d4abba2150ca83540528e4b868002e346004b2,
0x62660ecce5979493fe5684526e8e00875b948e507a89a47096bc84064a175452,
));
let signed_message = Message::from(0x1e45523606c96c98ba970ff7cf9511fab8b25e1bcd52ced30b81df1e4a9c4323);
// A verified public key
let secp256r1_verified = secp256r1_signature.verify(secp256r1_public_key, signed_message);
assert(secp256r1_verified.is_ok());
// Ed25519 Signature Verification
let ed25519_public_key = PublicKey::from(0x314fa58689bbe1da2430517de2d772b384a1c1d2e9cb87e73c6afcf246045b10);
let ed25519_signature = Signature::Ed25519(Ed25519::from((
0xf38cef9361894be6c6e0eddec28a663d099d7ddff17c8077a1447d7ecb4e6545,
0xf5084560039486d3462dd65a40c80a74709b2f06d450ffc5dc00345c6b2cdd00,
)));
let hashed_message = Message::from(sha256(b256::zero()));
// A verified public key
let ed25519_verified = ed25519_signature.verify(ed25519_public_key, hashed_message);
assert(ed25519_verified.is_ok());
// ANCHOR_END: signature_verification
}
fn address_verification() {
// ANCHOR: address_verification
// Secp256k1 Address Verification
let secp256k1_address = Address::from(0x02844f00cce0f608fa3f0f7408bec96bfd757891a6fda6e1fa0f510398304881);
let secp256k1_signature = Secp256k1::from((
0x61f3caf4c0912cec69ff0b226638d397115c623a7f057914d48a7e4daf1cf6d8,
0x2555de81cd3a40382d3d64eb1c77e463eea5a76d65ec85f283e0b3d568352678,
));
let signed_message = Message::from(0xa13f4ab54057ce064d3dd97ac3ff30ed704e73956896c03650fe59b1a561fe15);
// A verified address
let secp256k1_verified = secp256k1_signature.verify_address(secp256k1_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_address = Address::from(0xb4a5fabee8cc852084b71f17107e9c18d682033a58967027af0ab01edf2f9a6a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0xbd0c9b8792876713afa8bf3383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A verified address
let secp256r1_verified = secp256r1_signature.verify_address(secp256r1_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: address_verification
}
fn evm_address_verification() {
// ANCHOR: evm_address_verification
// Secp256k1 Address Verification
let secp256k1_evm_address = EvmAddress::from(0x0000000000000000000000000ec44cf95ce5051ef590e6d420f8e722dd160ecb);
let secp256k1_signature = Signature::Secp256k1(Secp256k1::from((
0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c,
0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d,
)));
let signed_message = Message::from(0xee45573606c96c98ba970ff7cf9511f1b8b25e6bcd52ced30b89df1e4a9c4323);
// A recovered EVM address.
let secp256k1_verified = secp256k1_signature.verify_evm_address(secp256k1_evm_address, signed_message);
assert(secp256k1_verified.is_ok());
// Secp256r1 Address Verification
let secp256r1_evm_address = EvmAddress::from(0x000000000000000000000000408eb2d97ef0beda0a33848d9e052066667cb00a);
let secp256r1_signature = Signature::Secp256r1(Secp256r1::from((
0x62CDC20C0AB6AA7B91E63DA9917792473F55A6F15006BC99DD4E29420084A3CC,
0xF4D99AF28F9D6BD96BDAAB83BFED99212AC3C7D06810E33FBB14C4F29B635414,
)));
let signed_message = Message::from(0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563);
// A recovered EVM address.
let secp256r1_verified = secp256r1_signature.verify_evm_address(secp256r1_evm_address, signed_message);
assert(secp256r1_verified.is_ok());
// ANCHOR_END: evm_address_verification
}
Storage
When developing a smart contract, you will typically need some sort of persistent storage. In this case, persistent storage, often just called storage in this context, is a place where you can store values that are persisted inside the contract itself. This is in contrast to a regular value in memory, which disappears after the contract exits.
Put in conventional programming terms, contract storage is like saving data to a hard drive. That data is saved even after the program that saved it exits. That data is persistent. Using memory is like declaring a variable in a program: it exists for the duration of the program and is non-persistent.
Some basic use cases of storage include declaring an owner address for a contract and saving balances in a wallet.
Storage Accesses Via the storage Keyword
Declaring variables in storage requires a storage block that contains a list of all your variables, their types, and their initial values. The initial value can be any expression that can be evaluated to a constant during compilation, as follows:
contract;
// ANCHOR: basic_storage_declaration
storage {
var1: u64 = 1,
var2: b256 = b256::zero(),
var3: Address = Address::zero(),
var4: Option<u8> = None,
}
// ANCHOR_END: basic_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_something();
#[storage(read)]
fn get_something();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_something() {
// ANCHOR: basic_storage_write
storage.var1.write(42);
storage
.var2
.write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.var3
.write(Address::from(0x1111111111111111111111111111111111111111111111111111111111111111));
storage.var4.write(Some(2u8));
// ANCHOR_END: basic_storage_write
}
#[storage(read)]
fn get_something() {
// ANCHOR: basic_storage_read
let var1: u64 = storage.var1.read();
let var2: b256 = storage.var2.try_read().unwrap_or(b256::zero());
let var3: Address = storage.var3.try_read().unwrap_or(Address::zero());
let var4: Option<u8> = storage.var4.try_read().unwrap_or(None);
// ANCHOR_END: basic_storage_read
}
}
To write into a storage variable, you need to use the storage keyword as follows:
contract;
// ANCHOR: basic_storage_declaration
storage {
var1: u64 = 1,
var2: b256 = b256::zero(),
var3: Address = Address::zero(),
var4: Option<u8> = None,
}
// ANCHOR_END: basic_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_something();
#[storage(read)]
fn get_something();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_something() {
// ANCHOR: basic_storage_write
storage.var1.write(42);
storage
.var2
.write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.var3
.write(Address::from(0x1111111111111111111111111111111111111111111111111111111111111111));
storage.var4.write(Some(2u8));
// ANCHOR_END: basic_storage_write
}
#[storage(read)]
fn get_something() {
// ANCHOR: basic_storage_read
let var1: u64 = storage.var1.read();
let var2: b256 = storage.var2.try_read().unwrap_or(b256::zero());
let var3: Address = storage.var3.try_read().unwrap_or(Address::zero());
let var4: Option<u8> = storage.var4.try_read().unwrap_or(None);
// ANCHOR_END: basic_storage_read
}
}
To read a storage variable, you also need to use the storage keyword. You may use read() or try_read(), however we recommend using try_read() for additional safety.
contract;
// ANCHOR: basic_storage_declaration
storage {
var1: u64 = 1,
var2: b256 = b256::zero(),
var3: Address = Address::zero(),
var4: Option<u8> = None,
}
// ANCHOR_END: basic_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_something();
#[storage(read)]
fn get_something();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_something() {
// ANCHOR: basic_storage_write
storage.var1.write(42);
storage
.var2
.write(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.var3
.write(Address::from(0x1111111111111111111111111111111111111111111111111111111111111111));
storage.var4.write(Some(2u8));
// ANCHOR_END: basic_storage_write
}
#[storage(read)]
fn get_something() {
// ANCHOR: basic_storage_read
let var1: u64 = storage.var1.read();
let var2: b256 = storage.var2.try_read().unwrap_or(b256::zero());
let var3: Address = storage.var3.try_read().unwrap_or(Address::zero());
let var4: Option<u8> = storage.var4.try_read().unwrap_or(None);
// ANCHOR_END: basic_storage_read
}
}
Storing Structs
To store a struct in storage, each variable must be assigned in the storage block. This can be either my assigning the fields individually or using a public constructor that can be evaluated to a constant during compilation.
contract;
// ANCHOR: struct_storage_declaration
struct Type1 {
x: u64,
y: u64,
}
struct Type2 {
w: b256,
z: bool,
}
impl Type2 {
// a constructor that evaluates to a constant during compilation
fn default() -> Self {
Self {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: true,
}
}
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2::default(),
}
// ANCHOR_END: struct_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_struct();
#[storage(read)]
fn get_struct();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_struct() {
// ANCHOR: struct_storage_write
// Store individual fields
storage.var1.x.write(42);
storage.var1.y.write(77);
// Store an entire struct
let new_struct = Type2 {
w: 0x1111111111111111111111111111111111111111111111111111111111111111,
z: false,
};
storage.var2.write(new_struct);
// ANCHOR_END: struct_storage_write
}
#[storage(read)]
fn get_struct() {
// ANCHOR: struct_storage_read
let var1_x: u64 = storage.var1.x.try_read().unwrap_or(0);
let var1_y: u64 = storage.var1.y.try_read().unwrap_or(0);
let var2: Type2 = storage.var2.try_read().unwrap_or(Type2::default());
// ANCHOR_END: struct_storage_read
}
}
You may write to both fields of a struct and the entire struct as follows:
contract;
// ANCHOR: struct_storage_declaration
struct Type1 {
x: u64,
y: u64,
}
struct Type2 {
w: b256,
z: bool,
}
impl Type2 {
// a constructor that evaluates to a constant during compilation
fn default() -> Self {
Self {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: true,
}
}
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2::default(),
}
// ANCHOR_END: struct_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_struct();
#[storage(read)]
fn get_struct();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_struct() {
// ANCHOR: struct_storage_write
// Store individual fields
storage.var1.x.write(42);
storage.var1.y.write(77);
// Store an entire struct
let new_struct = Type2 {
w: 0x1111111111111111111111111111111111111111111111111111111111111111,
z: false,
};
storage.var2.write(new_struct);
// ANCHOR_END: struct_storage_write
}
#[storage(read)]
fn get_struct() {
// ANCHOR: struct_storage_read
let var1_x: u64 = storage.var1.x.try_read().unwrap_or(0);
let var1_y: u64 = storage.var1.y.try_read().unwrap_or(0);
let var2: Type2 = storage.var2.try_read().unwrap_or(Type2::default());
// ANCHOR_END: struct_storage_read
}
}
The same applies to reading structs from storage, where both the individual and struct as a whole may be read as follows:
contract;
// ANCHOR: struct_storage_declaration
struct Type1 {
x: u64,
y: u64,
}
struct Type2 {
w: b256,
z: bool,
}
impl Type2 {
// a constructor that evaluates to a constant during compilation
fn default() -> Self {
Self {
w: 0x0000000000000000000000000000000000000000000000000000000000000000,
z: true,
}
}
}
storage {
var1: Type1 = Type1 { x: 0, y: 0 },
var2: Type2 = Type2::default(),
}
// ANCHOR_END: struct_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_struct();
#[storage(read)]
fn get_struct();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_struct() {
// ANCHOR: struct_storage_write
// Store individual fields
storage.var1.x.write(42);
storage.var1.y.write(77);
// Store an entire struct
let new_struct = Type2 {
w: 0x1111111111111111111111111111111111111111111111111111111111111111,
z: false,
};
storage.var2.write(new_struct);
// ANCHOR_END: struct_storage_write
}
#[storage(read)]
fn get_struct() {
// ANCHOR: struct_storage_read
let var1_x: u64 = storage.var1.x.try_read().unwrap_or(0);
let var1_y: u64 = storage.var1.y.try_read().unwrap_or(0);
let var2: Type2 = storage.var2.try_read().unwrap_or(Type2::default());
// ANCHOR_END: struct_storage_read
}
}
Common Storage Collections
We support the following common storage collections:
StorageMap<K, V>StorageVec<T>StorageBytesStorageString
Please note that these types are not initialized during compilation. This means that if you try to access a key from a storage map before the storage has been set, for example, the call will revert.
Declaring these variables in storage requires a storage block as follows:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
StorageMaps<K, V>
Generic storage maps are available in the standard library as StorageMap<K, V> which have to be defined inside a storage block and allow you to call insert() and get() to insert values at specific keys and get those values respectively. Refer to Storage Maps for more information about StorageMap<K, V>.
Warning While the StorageMap<K, V> is currently included in the prelude, to use it the Hash trait must still be imported. This is a known issue and will be resolved.
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
To write to a storage map, call either the insert() or try_insert() functions as follows:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
The following demonstrates how to read from a storage map:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
StorageVec<T>
Generic storage vectors are available in the standard library as StorageVec<T> which have to be defined inside a storage block and allow you to call push() and pop() to push and pop values from a vector respectively. Refer to Storage Vector for more information about StorageVec<T>.
The following demonstrates how to import StorageVec<T>:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
NOTE: When importing the
StorageVec<T>, please be sure to use the glob operator:use std::storage::storage_vec::*.
The following demonstrates how to write to a StorageVec<T>:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
The following demonstrates how to read from a StorageVec<T>:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
StorageBytes
Storage of Bytes is available in the standard library as StorageBytes which have to be defined inside a storage block. StorageBytes cannot be manipulated in the same way a StorageVec<T> or StorageMap<K, V> can but stores bytes more efficiently thus reducing gas. Only the entirety of a Bytes may be read/written to storage. This means any changes would require loading the entire Bytes to the heap, making changes, and then storing it once again. If frequent changes are needed, a StorageVec<u8> is recommended.
The following demonstrates how to import StorageBytes:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
NOTE: When importing the
StorageBytes, please be sure to use the glob operator:use std::storage::storage_bytes::*.
The following demonstrates how to write to a StorageBytes:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
The following demonstrates how to read from a StorageBytes:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
StorageString
Storage of String is available in the standard library as StorageString which have to be defined inside a storage block. StorageString cannot be manipulated in the same way a StorageVec<T> or StorageMap<K, V>. Only the entirety of a String may be read/written to storage.
The following demonstrates how to import StorageString:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
NOTE: When importing the
StorageString, please be sure to use the glob operator:use std::storage::storage_string::*.
The following demonstrates how to write to a StorageString:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
The following demonstrates how to read from a StorageString:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: temp_hash_import
use std::hash::Hash;
// ANCHOR: temp_hash_import
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR: storage_vec_import
// ANCHOR: storage_bytes_import
use std::storage::storage_bytes::*;
// ANCHOR: storage_bytes_import
// ANCHOR: storage_string_import
use std::storage::storage_string::*;
// ANCHOR: storage_string_import
// ANCHOR: advanced_storage_declaration
storage {
storage_map: StorageMap<u64, bool> = StorageMap {},
storage_vec: StorageVec<b256> = StorageVec {},
storage_string: StorageString = StorageString {},
storage_bytes: StorageBytes = StorageBytes {},
}
// ANCHOR_END: advanced_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map();
#[storage(read)]
fn get_map();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
#[storage(write)]
fn store_string();
#[storage(read)]
fn get_string();
#[storage(write)]
fn store_bytes();
#[storage(read)]
fn get_bytes();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map() {
// ANCHOR: map_storage_write
storage.storage_map.insert(12, true);
storage.storage_map.insert(59, false);
// try_insert() will only insert if a value does not already exist for a key.
let result = storage.storage_map.try_insert(103, true);
assert(result.is_ok());
// ANCHOR_END: map_storage_write
}
#[storage(read)]
fn get_map() {
// ANCHOR: map_storage_read
// Access directly
let stored_val1: bool = storage.storage_map.get(12).try_read().unwrap_or(false);
// First get the storage key and then access the value.
let storage_key2: StorageKey<bool> = storage.storage_map.get(59);
let stored_val2: bool = storage_key2.try_read().unwrap_or(false);
// Unsafely access the value.
let stored_val3: bool = storage.storage_map.get(103).read();
// ANCHOR_END: map_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: vec_storage_write
storage
.storage_vec
.push(0x1111111111111111111111111111111111111111111111111111111111111111);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000001);
storage
.storage_vec
.push(0x0000000000000000000000000000000000000000000000000000000000000002);
// Set will overwrite the element stored at the given index.
storage.storage_vec.set(2, b256::zero());
// ANCHOR_END: vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: vec_storage_read
// Method 1: Access the element directly
// Note: get() does not remove the element from the vec.
let stored_val1: b256 = storage.storage_vec.get(0).unwrap().try_read().unwrap_or(b256::zero());
// Method 2: First get the storage key and then access the value.
let storage_key2: StorageKey<b256> = storage.storage_vec.get(1).unwrap();
let stored_val2: b256 = storage_key2.try_read().unwrap_or(b256::zero());
// pop() will remove the last element from the vec.
let length: u64 = storage.storage_vec.len();
let stored_val3: b256 = storage.storage_vec.pop().unwrap();
assert(length != storage.storage_vec.len());
// ANCHOR_END: vec_storage_read
}
#[storage(write)]
fn store_string() {
// ANCHOR: string_storage_write
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.storage_string.write_slice(my_string);
// ANCHOR_END: string_storage_write
}
#[storage(read)]
fn get_string() {
// ANCHOR: string_storage_read
let stored_string: String = storage.storage_string.read_slice().unwrap();
// ANCHOR_END: string_storage_read
}
#[storage(write)]
fn store_bytes() {
// ANCHOR: bytes_storage_write
// Setup Bytes
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Write to storage
storage.storage_bytes.write_slice(my_bytes);
// ANCHOR_END: bytes_storage_write
}
#[storage(read)]
fn get_bytes() {
// ANCHOR: bytes_storage_read
let stored_bytes: Bytes = storage.storage_bytes.read_slice().unwrap();
// ANCHOR_END: bytes_storage_read
}
}
Advanced Storage
For more advanced storage techniques please refer to the Advanced Storage page.
Purity
A function is pure if it does not access any persistent storage. Conversely, the function is impure if it does access any storage. Naturally, as storage is only available in smart contracts, impure functions cannot be used in predicates, scripts, or libraries. A pure function cannot call an impure function.
In Sway, functions are pure by default but can be opted into impurity via the storage function attribute. The storage attribute may take read and/or write arguments indicating which type of access the function requires.
The storage attribute without any arguments, #[storage()], indicates a pure function, and has the same effect as not having the attribute at all.
#[storage(read)]
fn get_amount() -> u64 {
...
}
#[storage(read, write)]
fn increment_amount(increment: u64) -> u64 {
...
}
fn a_pure_function() {
...
}
#[storage()]
fn also_a_pure_function() {
...
}
Note: the
#[storage(write)]attribute also permits a function to read from storage. This is due to the fact that partially writing a storage slot requires first reading the slot.
Impure functions which call other impure functions must have at least the same storage privileges or a superset of those for the function called. For example, to call a function with write access a caller must also have write access, or both read and write access. To call a function with read and write access the caller must also have both privileges.
The storage attribute may also be applied to methods and associated functions, trait and ABI declarations.
A pure function gives you some guarantees: you will not incur excessive storage gas costs, the compiler can apply additional optimizations, and they are generally easy to reason about and audit.
Note: Purity does not provide an absolute guarantee that a storage access will not happen as a result of calling a pure function. E.g., it is possible for a pure function to call another contract, which can then call a write function in the original contract. The guarantee that the purity gives in this example is, that the original pure function itself does not change the storage, as well as that any function later called, that accesses storage, is clearly marked as impure.
A similar concept exists in Solidity. Note that Solidity refers to contract storage as contract state, and in the Sway/Fuel ecosystem, these two terms are largely interchangeable.
Identifiers
Addresses in Sway are similar to EVM addresses. The two major differences are:
- Sway addresses are 32 bytes long (instead of 20)
- Sway addresses are computed with the SHA-256 hash of the public key instead of the keccak-256 hash.
Contracts, on the other hand, are uniquely identified with a contract ID rather than an address. A contract's ID is also 32 bytes long and is calculated here.
Native Assets
The FuelVM has built-in support for working with multiple assets.
Key Differences Between EVM and FuelVM Assets
ERC-20 vs Native Asset
On the EVM, Ether is the native asset. As such, sending ETH to an address or contract is an operation built into the EVM, meaning it doesn't rely on the existence of a smart contract to update balances to track ownership as with ERC-20 tokens.
On the FuelVM, all assets are native and the process for sending any native asset is the same.
While you would still need a smart contract to handle the minting and burning of assets, the sending and receiving of these assets can be done independently of the asset contract.
Just like the EVM however, Fuel has a standard that describes a standard API for Native Assets using the Sway Language. The ERC-20 equivalent for the Sway Language is the SRC-20; Native Asset Standard.
NOTE It is important to note that Fuel does not have tokens.
ERC-721 vs Native Asset
On the EVM, an ERC-721 token or NFT is a contract that contains multiple tokens which are non-fungible with one another.
On the FuelVM, the ERC-721 equivalent is a Native Asset where each asset has a supply of one. This is defined in the SRC-20; Native Asset Standard under the Non-Fungible Asset Restrictions.
In practice, this means all NFTs are treated the same as any other Native Asset on Fuel. When writing Sway code, no additional cases for handling non-fungible and fungible assets are required.
No Token Approvals
An advantage Native Assets bring is that there is no need for token approvals; as with Ether on the EVM. With millions of dollars hacked every year due to misused token approvals, the FuelVM eliminates this attack vector.
Asset vs Coin vs Token
An "Asset" is a Native Asset on Fuel and has the associated AssetId type. Assets are distinguishable from one another. A "Coin" represents a singular unit of an Asset. Coins of the same Asset are not distinguishable from one another.
Fuel does not use tokens like other ecosystems such as Ethereum and uses Native Assets with a UTXO design instead.
The AssetId type
The AssetId type represents any Native Asset on Fuel. An AssetId is used for interacting with an asset on the network.
The AssetId of any Native Asset on Fuel is calculated by taking the SHA256 hash digest of the originating ContractId that minted the asset and a SubId i.e. sha256((contract_id, sub_id)).
Creating a New AssetId
There are 3 ways to instantiate a new AssetId:
Default
When a contract will only ever mint a single asset, it is recommended to use the DEFAULT_ASSET_ID sub id. This is referred to as the default asset of a contract.
To get the default asset from an internal contract call, call the default() function:
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
New
If a contract mints multiple assets or if the asset has been minted by an external contract, the new() function will be needed. The new() function takes the ContractId of the contract which minted the token as well as a SubId.
To create a new AssetId using a ContractId and SubId, call the new() function:
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
From
In the case where the b256 value of an asset is already known, you may call the from() function with the b256 value.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
The SubId type
The SubId is used to differentiate between different assets that are created by the same contract. The SubId is a b256 value.
When creating a single new asset on Fuel, we recommend using the DEFAULT_SUB_ID or SubId::zero().
The Base Asset
On the Fuel Network, the base asset is Ether. This is the only asset on the Fuel Network that does not have a SubId.
The Base Asset can be returned anytime by calling the base() function of the AssetId type.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
Basic Native Asset Functionality
Minting A Native Asset
To mint a new asset, the std::asset::mint() function must be called internally within a contract. A SubId and amount of coins must be provided. These newly minted coins will be owned by the contract which minted them. To mint another asset from the same contract, replace the DEFAULT_SUB_ID with your desired SubId.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
You may also mint an asset to a specific entity with the std::asset::mint_to() function. Be sure to provide a target Identity that will own the newly minted coins.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
If you intend to allow external users to mint assets using your contract, the SRC-3; Mint and Burn Standard defines a standard API for minting assets. The Sway-Libs Asset Library also provides an additional library to support implementations of the SRC-3 Standard into your contract.
Burning a Native Asset
To burn an asset, the std::asset::burn() function must be called internally from the contract which minted them. The SubId used to mint the coins and amount must be provided. The burned coins must be owned by the contract. When an asset is burned it doesn't exist anymore.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
If you intend to allow external users to burn assets using your contract, the SRC-3; Mint and Burn Standard defines a standard API for burning assets. The Sway-Libs Asset Library also provides an additional library to support implementations of the SRC-3 Standard into your contract.
Transfer a Native Asset
To internally transfer a Native Asset, the std::asset::transfer() function must be called. A target Identity or user must be provided as well as the AssetId of the asset and an amount.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
Native Asset And Transactions
Getting The Transaction Asset
To query for the Native Asset sent in a transaction, you may call the std::call_frames::msg_asset_id() function.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
Getting The Transaction Amount
To query for the amount of coins sent in a transaction, you may call the std::context::msg_amount() function.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
Native Assets and Contracts
Checking A Contract's Balance
To internally check a contract's balance, call the std::context::this_balance() function with the corresponding AssetId.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
To check the balance of an external contract, call the std::context::balance_of() function with the corresponding AssetId.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
NOTE Due to the FuelVM's UTXO design, balances of
Address's cannot be returned in the Sway Language. This must be done off-chain using the SDK.
Receiving Native Assets In A Contract
By default, a contract may not receive a Native Asset in a contract call. To allow transferring of assets to the contract, add the #[payable] attribute to the function.
contract;
use std::{asset::*, call_frames::msg_asset_id, constants::DEFAULT_SUB_ID, context::*};
abi NativeAsset {
fn mint_coins(mint_amount: u64);
fn burn_coins(burn_amount: u64);
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity);
#[payable]
fn deposit();
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
fn get_msg_amount();
fn this_balance(asset_id: AssetId) -> u64;
fn get_msg_asset_id();
fn mint_coins_to(target_identity: Identity, mint_amount: u64);
}
impl NativeAsset for Contract {
/// Mint an amount of this contracts native asset to the contracts balance.
fn mint_coins(mint_amount: u64) {
// ANCHOR: mint_asset
mint(DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_asset
}
fn mint_coins_to(target_identity: Identity, mint_amount: u64) {
// ANCHOR: mint_to_asset
mint_to(target_identity, DEFAULT_SUB_ID, mint_amount);
// ANCHOR_END: mint_to_asset
}
/// Burn an amount of this contracts native asset.
fn burn_coins(burn_amount: u64) {
// ANCHOR: burn_asset
burn(DEFAULT_SUB_ID, burn_amount);
// ANCHOR_END: burn_asset
}
/// Transfer coins to a target contract.
fn transfer_coins(coins: u64, asset_id: AssetId, target: Identity) {
// ANCHOR: transfer_asset
transfer(target, asset_id, coins);
// ANCHOR_END: transfer_asset
}
/// Get the internal balance of a specific coin at a specific contract.
fn get_balance(target_contract: ContractId, asset_id: AssetId) -> u64 {
// ANCHOR: balance_of
balance_of(target_contract, asset_id)
// ANCHOR_END: balance_of
}
/// Get the internal balance of a specific coin at a specific contract.
fn this_balance(asset_id: AssetId) -> u64 {
// ANCHOR: this_balance
this_balance(asset_id)
// ANCHOR_END: this_balance
}
/// Deposit coins back into the contract.
// ANCHOR: payable
#[payable]
fn deposit() {
assert(msg_amount() > 0);
}
// ANCHOR_END: payable
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_amount() {
// ANCHOR: msg_amount
let amount = msg_amount();
// ANCHOR_END: msg_amount
}
/// Mint and send this contracts native asset to a destination contract.
fn get_msg_asset_id() {
// ANCHOR: msg_asset_id
let amount = msg_asset_id();
// ANCHOR_END: msg_asset_id
}
}
fn get_base_asset() {
// ANCHOR: base_asset
let base_asset: AssetId = AssetId::base();
// ANCHOR_END: base_asset
}
fn default_asset_id() {
// ANCHOR: default_asset_id
let asset_id: AssetId = AssetId::default();
// ANCHOR_END: default_asset_id
}
fn new_asset_id(my_contract_id: ContractId, my_sub_id: SubId) {
// ANCHOR: new_asset_id
let my_contract_id: ContractId = ContractId::from(0x1000000000000000000000000000000000000000000000000000000000000000);
let my_sub_id: SubId = 0x2000000000000000000000000000000000000000000000000000000000000000;
let asset_id: AssetId = AssetId::new(my_contract_id, my_sub_id);
// ANCHOR_END: new_asset_id
}
fn from_asset_id() {
// ANCHOR: from_asset_id
let asset_id: AssetId = AssetId::from(0x0000000000000000000000000000000000000000000000000000000000000000);
// ANCHOR_END: from_asset_id
}
Native Asset Standards
There are a number of standards developed to enable further functionality for Native Assets and help cross contract functionality. Information on standards can be found in the Sway Standards Repo.
We currently have the following standards for Native Assets:
- SRC-20; Native Asset Standard defines the implementation of a standard API for Native Assets using the Sway Language.
- SRC-3; Mint and Burn Standard is used to enable mint and burn functionality for Native Assets.
- SRC-7; Arbitrary Asset Metadata Standard is used to store metadata for Native Assets.
- SRC-6; Vault Standard defines the implementation of a standard API for asset vaults developed in Sway.
Native Asset Libraries
Additional Libraries have been developed to allow you to quickly create an deploy dApps that follow the Sway Standards.
- Asset Library provides functionality to implement the SRC-20; Native Asset Standard, SRC-3; Mint and Burn Standard, and SRC-7; Arbitrary Asset Metadata Standard standards.
Single Native Asset Example
In this fully fleshed out example, we show a native asset contract which mints a single asset. This is the equivalent to the ERC-20 Standard use in Ethereum. Note there are no token approval functions.
It implements the SRC-20; Native Asset, SRC-3; Mint and Burn, and SRC-5; Ownership standards. It does not use any external libraries.
// ERC20 equivalent in Sway.
contract;
use standards::{
src3::SRC3,
src5::{
SRC5,
State,
AccessError,
},
src20::{
SetDecimalsEvent,
SetNameEvent,
SetSymbolEvent,
SRC20,
TotalSupplyEvent,
},
};
use std::{
asset::{
burn,
mint_to,
},
call_frames::msg_asset_id,
constants::DEFAULT_SUB_ID,
context::msg_amount,
string::String,
contract_id::ContractId
};
configurable {
DECIMALS: u8 = 9u8,
NAME: str[7] = __to_str_array("MyAsset"),
SYMBOL: str[5] = __to_str_array("MYTKN"),
}
storage {
total_supply: u64 = 0,
owner: State = State::Uninitialized,
}
// Native Asset Standard
impl SRC20 for Contract {
#[storage(read)]
fn total_assets() -> u64 {
1
}
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64> {
if asset == AssetId::default() {
Some(storage.total_supply.read())
} else {
None
}
}
#[storage(read)]
fn name(asset: AssetId) -> Option<String> {
if asset == AssetId::default() {
Some(String::from_ascii_str(from_str_array(NAME)))
} else {
None
}
}
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String> {
if asset == AssetId::default() {
Some(String::from_ascii_str(from_str_array(SYMBOL)))
} else {
None
}
}
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8> {
if asset == AssetId::default() {
Some(DECIMALS)
} else {
None
}
}
}
// Ownership Standard
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
storage.owner.read()
}
}
// Mint and Burn Standard
impl SRC3 for Contract {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64) {
require(sub_id.is_some() && sub_id.unwrap() == DEFAULT_SUB_ID, "incorrect-sub-id");
require_access_owner();
let new_supply = storage.total_supply.read() + amount;
storage
.total_supply
.write(new_supply);
mint_to(recipient, DEFAULT_SUB_ID, amount);
TotalSupplyEvent::new(
AssetId::default(),
new_supply,
msg_sender().unwrap()
).log();
}
#[storage(read, write)]
fn burn(sub_id: SubId, amount: u64) {
require(sub_id == DEFAULT_SUB_ID, "incorrect-sub-id");
require(msg_amount() >= amount, "incorrect-amount-provided");
require(
msg_asset_id() == AssetId::default(),
"incorrect-asset-provided",
);
require_access_owner();
let new_supply = storage.total_supply.read() - amount;
storage
.total_supply
.write(new_supply);
burn(DEFAULT_SUB_ID, amount);
TotalSupplyEvent::new(
AssetId::default(),
new_supply,
msg_sender().unwrap()
).log();
}
}
abi SingleAsset {
#[storage(read, write)]
fn constructor(owner_: Identity);
}
impl SingleAsset for Contract {
#[storage(read, write)]
fn constructor(owner_: Identity) {
require(storage.owner.read() == State::Uninitialized, "owner-initialized");
storage.owner.write(State::Initialized(owner_));
}
}
#[storage(read)]
fn require_access_owner() {
require(
storage.owner.read() == State::Initialized(msg_sender().unwrap()),
AccessError::NotOwner,
);
}
abi EmitSRC20Events {
fn emit_src20_events();
}
impl EmitSRC20Events for Contract {
fn emit_src20_events() {
// Metadata that is stored as a configurable should only be emitted once.
let asset = AssetId::default();
let sender = msg_sender().unwrap();
let name = Some(String::from_ascii_str(from_str_array(NAME)));
let symbol = Some(String::from_ascii_str(from_str_array(SYMBOL)));
SetNameEvent::new(asset, name, sender).log();
SetSymbolEvent::new(asset, symbol, sender).log();
SetDecimalsEvent::new(asset, DECIMALS, sender).log();
}
}
Multi Native Asset Example
In this fully fleshed out example, we show a native asset contract which mints multiple assets. This is the equivalent to the ERC-1155 Standard use in Ethereum. Note there are no token approval functions.
It implements the SRC-20; Native Asset, SRC-3; Mint and Burn, and SRC-5; Ownership standards. It does not use any external libraries.
// ERC1155 equivalent in Sway.
contract;
use standards::{
src5::{
SRC5,
State,
AccessError
},
src20::{
SetDecimalsEvent,
SetNameEvent,
SetSymbolEvent,
SRC20,
TotalSupplyEvent,
}
src3::SRC3,
};
use std::{
asset::{
burn,
mint_to,
},
call_frames::msg_asset_id,
hash::{
Hash,
},
context::this_balance,
storage::storage_string::*,
string::String,
contract_id::ContractId
};
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
owner: State = State::Uninitialized,
}
// Native Asset Standard
impl SRC20 for Contract {
#[storage(read)]
fn total_assets() -> u64 {
storage.total_assets.read()
}
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64> {
storage.total_supply.get(asset).try_read()
}
#[storage(read)]
fn name(asset: AssetId) -> Option<String> {
storage.name.get(asset).read_slice()
}
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String> {
storage.symbol.get(asset).read_slice()
}
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8> {
storage.decimals.get(asset).try_read()
}
}
// Mint and Burn Standard
impl SRC3 for Contract {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64) {
require(sub_id.is_some(), "Error: SubId is None");
require_access_owner();
let asset_id = AssetId::new(ContractId::this(), sub_id.unwrap());
let supply = storage.total_supply.get(asset_id).try_read();
if supply.is_none() {
storage.total_assets.write(storage.total_assets.try_read().unwrap_or(0) + 1);
}
let new_supply = supply.unwrap_or(0) + amount;
storage.total_supply.insert(asset_id, new_supply);
mint_to(recipient, sub_id, amount);
TotalSupplyEvent::new(
asset_id,
new_supply,
msg_sender().unwrap()
).log();
}
#[storage(read, write)]
fn burn(sub_id: SubId, amount: u64) {
require_access_owner();
let asset_id = AssetId::new(ContractId::this(), sub_id);
require(this_balance(asset_id) >= amount, "not-enough-coins");
let supply = storage.total_supply.get(asset_id).try_read();
let new_supply = supply.unwrap_or(0) - amount;
storage.total_supply.insert(asset_id, new_supply);
burn(sub_id, amount);
TotalSupplyEvent::new(
asset_id,
new_supply,
msg_sender().unwrap()
).log();
}
}
abi MultiAsset {
#[storage(read, write)]
fn constructor(owner_: Identity);
#[storage(read, write)]
fn set_name(asset: AssetId, name: Option<String>);
#[storage(read, write)]
fn set_symbol(asset: AssetId, symbol: Option<String>);
#[storage(read, write)]
fn set_decimals(asset: AssetId, decimals: u8);
}
impl MultiAsset for Contract {
#[storage(read, write)]
fn constructor(owner_: Identity) {
require(storage.owner.read() == State::Uninitialized, "owner-initialized");
storage.owner.write(State::Initialized(owner_));
}
#[storage(read, write)]
fn set_name(asset: AssetId, name: Option<String>) {
require_access_owner();
storage.name.insert(asset, StorageString {});
storage.name.get(asset).write_slice(name);
SetNameEvent::new(asset, name, msg_sender().unwrap()).log();
}
#[storage(read, write)]
fn set_symbol(asset: AssetId, symbol: Option<String>) {
require_access_owner();
storage.symbol.insert(asset, StorageString {});
storage.symbol.get(asset).write_slice(symbol);
SetSymbolEvent::new(asset, symbol, msg_sender().unwrap()).log();
}
#[storage(read, write)]
fn set_decimals(asset: AssetId, decimals: u8) {
require_access_owner();
storage.decimals.insert(asset, decimals);
SetDecimalsEvent::new(asset, decimals, msg_sender().unwrap()).log();
}
}
#[storage(read)]
fn require_access_owner() {
require(
storage.owner.read() == State::Initialized(msg_sender().unwrap()),
AccessError::NotOwner,
);
}
Access Control
Smart contracts require the ability to restrict access to and identify certain users or contracts. Unlike account-based blockchains, transactions in UTXO-based blockchains (i.e. Fuel) do not necessarily have a unique transaction sender. Additional logic is needed to handle this difference, and is provided by the standard library.
msg_sender
To deliver an experience akin to the EVM's access control, the std library provides a msg_sender function, which identifies a unique caller based upon the call and/or transaction input data.
contract;
abi MyOwnedContract {
fn receive(field_1: u64) -> bool;
}
const OWNER = Address::from(0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c);
impl MyOwnedContract for Contract {
fn receive(field_1: u64) -> bool {
let sender = msg_sender().unwrap();
if let Identity::Address(addr) = sender {
assert(addr == OWNER);
} else {
revert(0);
}
true
}
}
The msg_sender function works as follows:
- If the caller is a contract, then
Ok(Sender)is returned with theContractIdsender variant. - If the caller is external (i.e. from a script), then all coin input owners in the transaction are checked. If all owners are the same, then
Ok(Sender)is returned with theAddresssender variant. - If the caller is external and coin input owners are different, then the caller cannot be determined and a
Err(AuthError)is returned.
Contract Ownership
Many contracts require some form of ownership for access control. The SRC-5 Ownership Standard has been defined to provide an interoperable interface for ownership within contracts.
To accomplish this, use the Ownership Library to keep track of the owner. This allows setting and revoking ownership using the variants Some(..) and None respectively. This is better, safer, and more readable than using the Identity type directly where revoking ownership has to be done using some magic value such as b256::zero() or otherwise.
- The following is an example of how to properly lock a function such that only the owner may call a function:
contract;
// SRC-5 Ownership Standard `State` enum
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
// SRC-5 Ownership Standard `Ownership` struct
pub struct Ownership {
state: State,
}
// Skeleton implementation of the Ownership Library.
// The library can be found here https://github.com/FuelLabs/sway-libs/tree/master/libs/ownership
impl StorageKey<Ownership> {
fn renounce_ownership(self) {}
fn set_ownership(self, identity: Identity) {}
fn owner(self) -> State {
State::Uninitialized
}
fn only_owner(self) {}
}
impl Ownership {
fn initialized(identity: Identity) -> Self {
Self {
state: State::Initialized(identity),
}
}
}
abi OwnershipExample {
#[storage(write)]
fn revoke_ownership();
#[storage(write)]
fn set_owner(identity: Identity);
#[storage(read)]
fn owner() -> State;
#[storage(read)]
fn only_owner();
}
// ANCHOR: set_owner_example_storage
storage {
owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
}
// ANCHOR_END: set_owner_example_storage
impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.renounce_ownership();
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example_function
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.set_ownership(identity);
}
// ANCHOR_END: set_owner_example_function
// ANCHOR: get_owner_example
#[storage(read)]
fn owner() -> State {
storage.owner.owner()
}
// ANCHOR_END: get_owner_example
// ANCHOR: only_owner_example
#[storage(read)]
fn only_owner() {
storage.owner.only_owner();
// Do stuff here
}
// ANCHOR_END: only_owner_example
}
Setting ownership can be done in one of two ways; During compile time or run time.
- The following is an example of how to properly set ownership of a contract during compile time:
contract;
// SRC-5 Ownership Standard `State` enum
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
// SRC-5 Ownership Standard `Ownership` struct
pub struct Ownership {
state: State,
}
// Skeleton implementation of the Ownership Library.
// The library can be found here https://github.com/FuelLabs/sway-libs/tree/master/libs/ownership
impl StorageKey<Ownership> {
fn renounce_ownership(self) {}
fn set_ownership(self, identity: Identity) {}
fn owner(self) -> State {
State::Uninitialized
}
fn only_owner(self) {}
}
impl Ownership {
fn initialized(identity: Identity) -> Self {
Self {
state: State::Initialized(identity),
}
}
}
abi OwnershipExample {
#[storage(write)]
fn revoke_ownership();
#[storage(write)]
fn set_owner(identity: Identity);
#[storage(read)]
fn owner() -> State;
#[storage(read)]
fn only_owner();
}
// ANCHOR: set_owner_example_storage
storage {
owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
}
// ANCHOR_END: set_owner_example_storage
impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.renounce_ownership();
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example_function
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.set_ownership(identity);
}
// ANCHOR_END: set_owner_example_function
// ANCHOR: get_owner_example
#[storage(read)]
fn owner() -> State {
storage.owner.owner()
}
// ANCHOR_END: get_owner_example
// ANCHOR: only_owner_example
#[storage(read)]
fn only_owner() {
storage.owner.only_owner();
// Do stuff here
}
// ANCHOR_END: only_owner_example
}
- The following is an example of how to properly set ownership of a contract during run time:
contract;
// SRC-5 Ownership Standard `State` enum
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
// SRC-5 Ownership Standard `Ownership` struct
pub struct Ownership {
state: State,
}
// Skeleton implementation of the Ownership Library.
// The library can be found here https://github.com/FuelLabs/sway-libs/tree/master/libs/ownership
impl StorageKey<Ownership> {
fn renounce_ownership(self) {}
fn set_ownership(self, identity: Identity) {}
fn owner(self) -> State {
State::Uninitialized
}
fn only_owner(self) {}
}
impl Ownership {
fn initialized(identity: Identity) -> Self {
Self {
state: State::Initialized(identity),
}
}
}
abi OwnershipExample {
#[storage(write)]
fn revoke_ownership();
#[storage(write)]
fn set_owner(identity: Identity);
#[storage(read)]
fn owner() -> State;
#[storage(read)]
fn only_owner();
}
// ANCHOR: set_owner_example_storage
storage {
owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
}
// ANCHOR_END: set_owner_example_storage
impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.renounce_ownership();
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example_function
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.set_ownership(identity);
}
// ANCHOR_END: set_owner_example_function
// ANCHOR: get_owner_example
#[storage(read)]
fn owner() -> State {
storage.owner.owner()
}
// ANCHOR_END: get_owner_example
// ANCHOR: only_owner_example
#[storage(read)]
fn only_owner() {
storage.owner.only_owner();
// Do stuff here
}
// ANCHOR_END: only_owner_example
}
- The following is an example of how to properly revoke ownership of a contract:
contract;
// SRC-5 Ownership Standard `State` enum
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
// SRC-5 Ownership Standard `Ownership` struct
pub struct Ownership {
state: State,
}
// Skeleton implementation of the Ownership Library.
// The library can be found here https://github.com/FuelLabs/sway-libs/tree/master/libs/ownership
impl StorageKey<Ownership> {
fn renounce_ownership(self) {}
fn set_ownership(self, identity: Identity) {}
fn owner(self) -> State {
State::Uninitialized
}
fn only_owner(self) {}
}
impl Ownership {
fn initialized(identity: Identity) -> Self {
Self {
state: State::Initialized(identity),
}
}
}
abi OwnershipExample {
#[storage(write)]
fn revoke_ownership();
#[storage(write)]
fn set_owner(identity: Identity);
#[storage(read)]
fn owner() -> State;
#[storage(read)]
fn only_owner();
}
// ANCHOR: set_owner_example_storage
storage {
owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
}
// ANCHOR_END: set_owner_example_storage
impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.renounce_ownership();
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example_function
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.set_ownership(identity);
}
// ANCHOR_END: set_owner_example_function
// ANCHOR: get_owner_example
#[storage(read)]
fn owner() -> State {
storage.owner.owner()
}
// ANCHOR_END: get_owner_example
// ANCHOR: only_owner_example
#[storage(read)]
fn only_owner() {
storage.owner.only_owner();
// Do stuff here
}
// ANCHOR_END: only_owner_example
}
- The following is an example of how to properly retrieve the state of ownership:
contract;
// SRC-5 Ownership Standard `State` enum
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
// SRC-5 Ownership Standard `Ownership` struct
pub struct Ownership {
state: State,
}
// Skeleton implementation of the Ownership Library.
// The library can be found here https://github.com/FuelLabs/sway-libs/tree/master/libs/ownership
impl StorageKey<Ownership> {
fn renounce_ownership(self) {}
fn set_ownership(self, identity: Identity) {}
fn owner(self) -> State {
State::Uninitialized
}
fn only_owner(self) {}
}
impl Ownership {
fn initialized(identity: Identity) -> Self {
Self {
state: State::Initialized(identity),
}
}
}
abi OwnershipExample {
#[storage(write)]
fn revoke_ownership();
#[storage(write)]
fn set_owner(identity: Identity);
#[storage(read)]
fn owner() -> State;
#[storage(read)]
fn only_owner();
}
// ANCHOR: set_owner_example_storage
storage {
owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
}
// ANCHOR_END: set_owner_example_storage
impl OwnershipExample for Contract {
// ANCHOR: revoke_owner_example
#[storage(write)]
fn revoke_ownership() {
storage.owner.renounce_ownership();
}
// ANCHOR_END: revoke_owner_example
// ANCHOR: set_owner_example_function
#[storage(write)]
fn set_owner(identity: Identity) {
storage.owner.set_ownership(identity);
}
// ANCHOR_END: set_owner_example_function
// ANCHOR: get_owner_example
#[storage(read)]
fn owner() -> State {
storage.owner.owner()
}
// ANCHOR_END: get_owner_example
// ANCHOR: only_owner_example
#[storage(read)]
fn only_owner() {
storage.owner.only_owner();
// Do stuff here
}
// ANCHOR_END: only_owner_example
}
Access Control Libraries
Sway-Libs provides the following libraries to enable further access control.
- Ownership Library; used to apply restrictions on functions such that only a single user may call them. This library provides helper functions for the SRC-5; Ownership Standard.
- Admin Library; used to apply restrictions on functions such that only a select few users may call them like a whitelist.
- Pausable Library; allows contracts to implement an emergency stop mechanism.
- Reentrancy Guard Library; used to detect and prevent reentrancy attacks.
Calling Contracts
Smart contracts can be called by other contracts or scripts. In the FuelVM, this is done primarily with the call instruction.
Sway provides a nice way to manage callable interfaces with its ABI system. The Fuel ABI specification can be found here.
Example
Here is an example of a contract calling another contract in Sway. A script can call a contract in the same way.
// ./contract_a.sw
contract;
abi ContractA {
fn receive(field_1: bool, field_2: u64) -> u64;
}
impl ContractA for Contract {
fn receive(field_1: bool, field_2: u64) -> u64 {
assert(field_1 == true);
assert(field_2 > 0);
return_45()
}
}
fn return_45() -> u64 {
45
}
// ./contract_b.sw
contract;
use contract_a::ContractA;
abi ContractB {
fn make_call();
}
const contract_id = 0x79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0;
impl ContractB for Contract {
fn make_call() {
let x = abi(ContractA, contract_id);
let return_value = x.receive(true, 3); // will be 45
}
}
Note: The ABI is for external calls only therefore you cannot define a method in the ABI and call it in the same contract. If you want to define a function for a contract, but keep it private so that only your contract can call it, you can define it outside of the
impland call it inside the contract, similar to thereturn_45()function above.
Advanced Calls
All calls forward a gas stipend, and may additionally forward one native asset with the call.
Here is an example of how to specify the amount of gas (gas), the asset ID of the native asset (asset_id), and the amount of the native asset (coins) to forward:
script;
abi MyContract {
fn foo(field_1: bool, field_2: u64);
}
fn main() {
let x = abi(MyContract, 0x79fa8779bed2f36c3581d01c79df8da45eee09fac1fd76a5a656e16326317ef0);
let asset_id = 0x7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777_7777;
x.foo {
gas: 5000, asset_id: asset_id, coins: 5000
}
(true, 3);
}
Handling Re-entrancy
A common attack vector for smart contracts is re-entrancy. Similar to the EVM, the FuelVM allows for re-entrancy.
A stateless re-entrancy guard is included in the sway-libs library. The guard will panic (revert) at run time if re-entrancy is detected.
contract;
use reentrancy::reentrancy_guard;
abi MyContract {
fn some_method();
}
impl ContractB for Contract {
fn some_method() {
reentrancy_guard();
// do something
}
}
CEI pattern violation static analysis
Another way of avoiding re-entrancy-related attacks is to follow the so-called CEI pattern. CEI stands for "Checks, Effects, Interactions", meaning that the contract code should first perform safety checks, also known as "pre-conditions", then perform effects, i.e. modify or read the contract storage and execute external contract calls (interaction) only at the very end of the function/method.
Please see this blog post for more detail on some vulnerabilities in case of storage modification after interaction and this blog post for more information on storage reads after interaction.
The Sway compiler implements a check that the CEI pattern is not violated in the user contract and issues warnings if that's the case.
For example, in the following contract the CEI pattern is violated, because an external contract call is executed before a storage write.
contract;
mod other_contract;
use other_contract::*;
use std::hash::*;
abi MyContract {
#[storage(read, write)]
fn withdraw(external_contract_id: ContractId);
}
storage {
balances: StorageMap<Identity, u64> = StorageMap::<Identity, u64> {},
}
impl MyContract for Contract {
#[storage(read, write)]
fn withdraw(external_contract_id: ContractId) {
let sender = msg_sender().unwrap();
let bal = storage.balances.get(sender).try_read().unwrap_or(0);
assert(bal > 0);
// External call
let caller = abi(OtherContract, external_contract_id.into());
caller.external_call {
coins: bal,
}();
// Storage update _after_ external call
storage.balances.insert(sender, 0);
}
}
Here, other_contract is defined as follows:
library;
abi OtherContract {
#[payable]
fn external_call();
}
The CEI pattern analyzer issues a warning as follows, pointing to the interaction before a storage modification:
warning
--> /path/to/contract/main.sw:28:9
|
26 |
27 | let caller = abi(OtherContract, external_contract_id.into());
28 | caller.external_call { coins: bal }();
| _________-
29 | |
30 | | // Storage update _after_ external call
31 | | storage.balances.insert(sender, 0);
| |__________________________________________- Storage write after external contract interaction in function or method "withdraw". Consider making all storage writes before calling another contract
32 | }
33 | }
|
____
In case there is a storage read after an interaction, the CEI analyzer will issue a similar warning.
In addition to storage reads and writes after an interaction, the CEI analyzer reports analogous warnings about:
- balance tree updates, i.e. balance tree reads with subsequent writes, which may be produced by the
trandtroASM instructions or library functions using them under the hood; - balance trees reads with
balinstruction; - changes to the output messages that can be produced by the
__smointrinsic function or thesmoASM instruction.
Differences from the EVM
While the Fuel contract calling paradigm is similar to the EVM's (using an ABI, forwarding gas and data), it differs in two key ways:
-
Native assets: FuelVM calls can forward any native asset not just base asset.
-
No data serialization: Contract calls in the FuelVM do not need to serialize data to pass it between contracts; instead they simply pass a pointer to the data. This is because the FuelVM has a shared global memory which all call frames can read from.
Fallback
When a contract is compiled, a special section called "contract selection" is also generated. This section checks if the contract call method matches any of the available ABI methods. If this fails, one of two possible actions will happen:
1 - if no fallback function was specified, the contract will revert; 2 - otherwise, the fallback function will be called.
For all intents and purposes the fallback function is considered a contract method, which means that it has all the limitations that other contract methods have. As the fallback function signature, the function cannot have arguments, but they can return anything.
If for some reason the fallback function needs to returns different types, the intrinsic __contract_ret can be used.
contract;
abi MyContract {
fn some_method();
}
impl ContractB for Contract {
fn some_method() {
}
}
#[fallback]
fn fallback() {
}
You may still access the method selector and arguments to the call in the fallback.
For instance, let's assume a function fn foobar(bool, u64) {} gets called on a contract that doesn't have it with arguments true and 42.
It can execute the following fallback:
#[fallback]
fn fallback() {
// the method selector is the first four bytes of sha256("foobar(bool,u64)")
// per https://fuellabs.github.io/fuel-specs/master/protocol/abi#function-selector-encoding
let method_selector = std::call_frames::first_param::<u64>();
// the arguments tuple is (true, 42)
let arguments = std::call_frames::second_param::<(bool, u64)>();
}
External Code Execution
The std-lib includes a function called run_external that allows Sway contracts to execute arbitrary external Sway code.
This functionality enables features like upgradeable contracts and proxies.
Upgradeable Contracts
Upgradeable contracts are designed to allow the logic of a smart contract to be updated after deployment.
Consider this example proxy contract:
contract;
use std::execution::run_external;
abi Proxy {
#[storage(write)]
fn set_target_contract(id: ContractId);
#[storage(read)]
fn double_input(_value: u64) -> u64;
}
// ANCHOR: proxy
#[namespace(my_storage_namespace)]
storage {
target_contract: Option<ContractId> = None,
}
impl Proxy for Contract {
#[storage(write)]
fn set_target_contract(id: ContractId) {
storage.target_contract.write(Some(id));
}
#[storage(read)]
fn double_input(_value: u64) -> u64 {
let target = storage.target_contract.read().unwrap();
run_external(target)
}
}
// ANCHOR_END: proxy
The contract has two functions:
set_target_contractupdates thetarget_contractvariable in storage with theContractIdof an external contract.double_inputreads thetarget_contractfrom storage and uses it to run external code. If thetarget_contracthas a function with the same name (double_input), the code in the externaldouble_inputfunction will run. In this case, the function will return au64.
Notice in the Proxy example above, the storage block has a namespace attribute. Using this attribute is considered a best practice for all proxy contracts in Sway, because it will prevent storage collisions with the implementation contract, as the implementation contract has access to both storage contexts.
Below is what an implementation contract could look like for this:
contract;
abi Implementation {
#[storage(write)]
fn double_input(value: u64) -> u64;
}
// ANCHOR: target
storage {
value: u64 = 0,
// to stay compatible, this has to stay the same in the next version
}
impl Implementation for Contract {
#[storage(write)]
fn double_input(value: u64) -> u64 {
let new_value = value * 2;
storage.value.write(new_value);
new_value
}
}
// ANCHOR_END: target
This contract has one function called double_input, which calculates the input value times two, updates the value variable in storage, and returns the new value.
How does this differ from calling a contract?
There are a couple of major differences between calling a contract directly and using the run_external method.
First, to use run_external, the ABI of the external contract is not required. The proxy contract has no knowledge of the external contract except for its ContractId.
Upgradeable Contract Storage
Second, the storage context of the proxy contract is retained for the loaded code.
This means that in the examples above, the value variable gets updated in the storage for the proxy contract.
For example, if you were to read the value variable by directly calling the implementation contract, you would get a different result than if you read it through the proxy contract.
The proxy contract loads the code and executes it in its own context.
Fallback functions
If the function name doesn't exist in the target contract but a fallback function does, the fallback function will be triggered.
If there is no fallback function, the transaction will revert.
You can access function parameters for fallback functions using the call_frames module in the std-lib.
For example, to access the _foo input parameter in the proxy function below, you can use the called_args method in the fallback function:
contract;
use std::execution::run_external;
configurable {
TARGET: ContractId = ContractId::zero(),
}
abi RunExternalTest {
fn double_value(foo: u64) -> u64;
fn large_value() -> b256;
fn does_not_exist_in_the_target(foo: u64) -> u64;
}
impl RunExternalTest for Contract {
fn double_value(_foo: u64) -> u64 {
__log(1);
run_external(TARGET)
}
fn large_value() -> b256 {
run_external(TARGET)
}
// ANCHOR: does_not_exist_in_the_target
fn does_not_exist_in_the_target(_foo: u64) -> u64 {
run_external(TARGET)
}
// ANCHOR_END: does_not_exist_in_the_target
}
contract;
abi RunExternalTest {
fn double_value(foo: u64) -> u64;
fn large_value() -> b256;
}
impl RunExternalTest for Contract {
fn double_value(foo: u64) -> u64 {
__log(2);
foo * 2
}
fn large_value() -> b256 {
0x00000000000000000000000059F2f1fCfE2474fD5F0b9BA1E73ca90b143Eb8d0
}
}
// ANCHOR: fallback
#[fallback]
fn fallback() -> u64 {
use std::call_frames::*;
__log(3);
__log(called_method());
__log("double_value");
__log(called_method() == "double_value");
let foo = called_args::<u64>();
foo * 3
}
// ANCHOR_END: fallback
In this case, the does_not_exist_in_the_target function will return _foo * 3.
Limitations
Some limitations of run_external function are:
- It can only be used with other contracts. Scripts, predicates, and library code cannot be run externally.
- If you change the implementation contract, you must maintain the same order of previous storage variables and types, as this is what has been stored in the proxy storage.
- You can't use the call stack in another call frame before you use
run_external. You can only use the call stack within the call frame that containsrun_external.
Advanced Concepts
Advanced concepts.
- Advanced Types
- Advanced Storage
- Generic Types
- Traits
- Associated Types
- Generics and Trait Constraints
- Assembly
- Never Type
Advanced Types
Creating Type Synonyms with Type Aliases
Sway provides the ability to declare a type alias to give an existing type another name. For this we use the type keyword. For example, we can create the alias Kilometers to u64 like so:
script;
// ANCHOR: type_alias
type Kilometers = u64;
// ANCHOR_END: type_alias
struct MyStruct<T, U> {
x: T,
y: U,
}
// ANCHOR: long_type_use
fn foo_long(array: [MyStruct<u64, b256>; 5]) -> [MyStruct<u64, b256>; 5] {
array
}
// ANCHOR_END: long_type_use
// ANCHOR: long_type_use_shorter
type MyArray = [MyStruct<u64, b256>; 5];
fn foo_shorter(array: MyArray) -> MyArray {
array
}
// ANCHOR_END: long_type_use_shorter
fn main() {
// ANCHOR: addition
let x: u64 = 5;
let y: Kilometers = 5;
assert(x + y == 10);
// ANCHOR_END: addition
}
Now, the alias Kilometers is a synonym for u64. Note that Kilometers is not a separate new type. Values that have the type Kilometers will be treated the same as values of type u64:
script;
// ANCHOR: type_alias
type Kilometers = u64;
// ANCHOR_END: type_alias
struct MyStruct<T, U> {
x: T,
y: U,
}
// ANCHOR: long_type_use
fn foo_long(array: [MyStruct<u64, b256>; 5]) -> [MyStruct<u64, b256>; 5] {
array
}
// ANCHOR_END: long_type_use
// ANCHOR: long_type_use_shorter
type MyArray = [MyStruct<u64, b256>; 5];
fn foo_shorter(array: MyArray) -> MyArray {
array
}
// ANCHOR_END: long_type_use_shorter
fn main() {
// ANCHOR: addition
let x: u64 = 5;
let y: Kilometers = 5;
assert(x + y == 10);
// ANCHOR_END: addition
}
Because Kilometers and u64 are the same type, we can add values of both types and we can pass Kilometers values to functions that take u64 parameters. However, using this method, we don’t get the type checking benefits that we get from introducing a separate new type called Kilometers. In other words, if we mix up Kilometers and i32 values somewhere, the compiler will not give us an error.
The main use case for type synonyms is to reduce repetition. For example, we might have a lengthy array type like this:
[MyStruct<u64, b256>; 5]
Writing this lengthy type in function signatures and as type annotations all over the code can be tiresome and error prone. Imagine having a project full of code like this:
script;
// ANCHOR: type_alias
type Kilometers = u64;
// ANCHOR_END: type_alias
struct MyStruct<T, U> {
x: T,
y: U,
}
// ANCHOR: long_type_use
fn foo_long(array: [MyStruct<u64, b256>; 5]) -> [MyStruct<u64, b256>; 5] {
array
}
// ANCHOR_END: long_type_use
// ANCHOR: long_type_use_shorter
type MyArray = [MyStruct<u64, b256>; 5];
fn foo_shorter(array: MyArray) -> MyArray {
array
}
// ANCHOR_END: long_type_use_shorter
fn main() {
// ANCHOR: addition
let x: u64 = 5;
let y: Kilometers = 5;
assert(x + y == 10);
// ANCHOR_END: addition
}
A type alias makes this code more manageable by reducing the repetition. Below, we’ve introduced an alias named MyArray for the verbose type and can replace all uses of the type with the shorter alias MyArray:
script;
// ANCHOR: type_alias
type Kilometers = u64;
// ANCHOR_END: type_alias
struct MyStruct<T, U> {
x: T,
y: U,
}
// ANCHOR: long_type_use
fn foo_long(array: [MyStruct<u64, b256>; 5]) -> [MyStruct<u64, b256>; 5] {
array
}
// ANCHOR_END: long_type_use
// ANCHOR: long_type_use_shorter
type MyArray = [MyStruct<u64, b256>; 5];
fn foo_shorter(array: MyArray) -> MyArray {
array
}
// ANCHOR_END: long_type_use_shorter
fn main() {
// ANCHOR: addition
let x: u64 = 5;
let y: Kilometers = 5;
assert(x + y == 10);
// ANCHOR_END: addition
}
This code is much easier to read and write! Choosing a meaningful name for a type alias can help communicate your intent as well.
Advanced Storage
Nested Storage Collections
Through the use of StorageKeys, you may have nested storage collections such as storing a StorageString in a StorageMap<K, V>.
For example, here we have a few common nested storage types declared in a storage block:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
Please note that storage initialization is needed to do this.
NOTE: When importing a storage type, please be sure to use the glob operator i.e.
use std::storage::storage_vec::*.
Storing a StorageVec<T> in a StorageMap<K, V>
The following demonstrates how to write to a StorageVec<T> that is nested in a StorageMap<T, V>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
The following demonstrates how to read from a StorageVec<T> that is nested in a StorageMap<T, V>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
Storing a StorageString in a StorageMap<K, V>
The following demonstrates how to write to a StorageString that is nested in a StorageMap<T, V>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
The following demonstrates how to read from a StorageString that is nested in a StorageMap<T, V>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
Storing a StorageBytes in a StorageVec<T>
The following demonstrates how to write to a StorageBytes that is nested in a StorageVec<T>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
The following demonstrates how to read from a StorageBytes that is nested in a StorageVec<T>:
contract;
use std::{
bytes::Bytes,
hash::{
Hash,
sha256,
},
storage::{
storage_bytes::*,
storage_string::*,
storage_vec::*,
},
string::String,
};
// ANCHOR: nested_storage_declaration
storage {
nested_map_vec: StorageMap<u64, StorageVec<u8>> = StorageMap {},
nested_map_string: StorageMap<u64, StorageString> = StorageMap {},
nested_vec_bytes: StorageVec<StorageBytes> = StorageVec {},
}
// ANCHOR_END: nested_storage_declaration
abi StorageExample {
#[storage(write)]
fn store_map_vec();
#[storage(read, write)]
fn get_map_vec();
#[storage(write)]
fn store_map_string();
#[storage(read)]
fn get_map_string();
#[storage(write)]
fn store_vec();
#[storage(read, write)]
fn get_vec();
}
impl StorageExample for Contract {
#[storage(write)]
fn store_map_vec() {
// ANCHOR: nested_vec_storage_write
// Setup and initialize storage for the StorageVec.
storage.nested_map_vec.try_insert(10, StorageVec {});
// Method 1: Push to the vec directly
storage.nested_map_vec.get(10).push(1u8);
storage.nested_map_vec.get(10).push(2u8);
storage.nested_map_vec.get(10).push(3u8);
// Method 2: First get the storage key and then push the values.
let storage_key_vec: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
storage_key_vec.push(4u8);
storage_key_vec.push(5u8);
storage_key_vec.push(6u8);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_map_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the StorageVec directly.
let stored_val1: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val2: u8 = storage.nested_map_vec.get(10).pop().unwrap();
let stored_val3: u8 = storage.nested_map_vec.get(10).pop().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageVec<u8>> = storage.nested_map_vec.get(10);
let stored_val4: u8 = storage_key.pop().unwrap();
let stored_val5: u8 = storage_key.pop().unwrap();
let stored_val6: u8 = storage_key.pop().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
#[storage(write)]
fn store_map_string() {
// ANCHOR: nested_string_storage_write
// Setup and initialize storage for the StorageString.
storage.nested_map_string.try_insert(10, StorageString {});
// Method 1: Store the string directly.
let my_string = String::from_ascii_str("Fuel is blazingly fast");
storage.nested_map_string.get(10).write_slice(my_string);
// Method 2: First get the storage key and then write the value.
let my_string = String::from_ascii_str("Fuel is modular");
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
storage_key.write_slice(my_string);
// ANCHOR_END: nested_string_storage_write
}
#[storage(read)]
fn get_map_string() {
// ANCHOR: nested_string_storage_read
// Method 1: Access the string directly.
let stored_string: String = storage.nested_map_string.get(10).read_slice().unwrap();
// Method 2: First get the storage key and then access the value.
let storage_key: StorageKey<StorageString> = storage.nested_map_string.get(10);
let stored_string: String = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_string_storage_read
}
#[storage(write)]
fn store_vec() {
// ANCHOR: nested_vec_storage_write
// Setup Bytes to store
let mut my_bytes = Bytes::new();
my_bytes.push(1u8);
my_bytes.push(2u8);
my_bytes.push(3u8);
// Setup and initialize storage for the StorageBytes.
storage.nested_vec_bytes.push(StorageBytes {});
// Method 1: Store the bytes by accessing StorageBytes directly.
storage
.nested_vec_bytes
.get(0)
.unwrap()
.write_slice(my_bytes);
// Method 2: First get the storage key and then write the bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
storage_key.write_slice(my_bytes);
// ANCHOR_END: nested_vec_storage_write
}
#[storage(read, write)]
fn get_vec() {
// ANCHOR: nested_vec_storage_read
// Method 1: Access the stored bytes directly.
let stored_bytes: Bytes = storage.nested_vec_bytes.get(0).unwrap().read_slice().unwrap();
// Method 2: First get the storage key and then access the stored bytes.
let storage_key: StorageKey<StorageBytes> = storage.nested_vec_bytes.get(0).unwrap();
let stored_bytes: Bytes = storage_key.read_slice().unwrap();
// ANCHOR_END: nested_vec_storage_read
}
}
Storage Namespace
If you want the values in storage to be positioned differently, for instance to avoid collisions with storage from another contract when loading code, you can use the namespace annotation to add a salt to the slot calculations.
contract;
use std::storage::storage_api::{read, write};
// ANCHOR: storage_namespace
storage {
example_namespace {
foo: u64 = 0,
},
// ANCHOR_END: storage_namespace
}
abi StorageNamespaceExample {
#[storage(write)]
fn store_something(amount: u64);
#[storage(read)]
fn get_something() -> u64;
}
impl StorageNamespaceExample for Contract {
#[storage(write)]
fn store_something(amount: u64) {
storage.foo.write(amount);
}
#[storage(read)]
fn get_something() -> u64 {
storage.foo.try_read().unwrap_or(0)
}
}
Manual Storage Management
It is possible to leverage FuelVM storage operations directly using the std::storage::storage_api::write and std::storage::storage_api::read functions provided in the standard library. With this approach, you will have to manually assign the internal key used for storage. An example is as follows:
contract;
use std::storage::storage_api::{read, write};
abi StorageExample {
#[storage(write)]
fn store_something(amount: u64);
#[storage(read)]
fn get_something() -> u64;
}
const STORAGE_KEY: b256 = 0x0000000000000000000000000000000000000000000000000000000000000000;
impl StorageExample for Contract {
#[storage(write)]
fn store_something(amount: u64) {
write(STORAGE_KEY, 0, amount);
}
#[storage(read)]
fn get_something() -> u64 {
let value: Option<u64> = read::<u64>(STORAGE_KEY, 0);
value.unwrap_or(0)
}
}
Note: Though these functions can be used for any data type, they should mostly be used for arrays because arrays are not yet supported in
storageblocks. Note, however, that all data types can be used as types for keys and/or values inStorageMap<K, V>without any restrictions.
Generic Types
Basics
In Sway, generic types follow a very similar pattern to those in Rust. Let's look at some example syntax, starting with a generic function:
fn noop<T>(argument: T) -> T {
argument
}
Here, the noop() function trivially returns exactly what was given to it. T is a type parameter, and it says
that this function exists for all types T. More formally, this function could be typed as:
noop :: ∀T. T -> T
Generic types are a way to refer to types in general, meaning without specifying a single type. Our noop function
would work with any type in the language, so we don't need to specify noop(argument: u8) -> u8, noop(argument: u16) -> u16, etc.
Code Generation
One question that arises when dealing with generic types is: how does the assembly handle this? There are a few approaches to handling generic types at the lowest level. Sway uses a technique called monomorphization. This means that the generic function is compiled to a non-generic version for every type it is called on. In this way, generic functions are purely shorthand for the sake of ergonomics.
Trait Constraints
An important background to know before diving into trait constraints is that the where clause can be used to specify the required traits for the generic argument. So, when writing something like a HashMap you may
want to specify that the generic argument implements a Hash trait.
fn get_hashmap_key<T>(key: T) -> b256
where T: Hash
{
// Code within here can then call methods associated with the Hash trait on Key
}
Of course, our noop() function is not useful. Often, a programmer will want to declare functions over types which satisfy certain traits.
For example, let's try to implement the successor function, successor(), for all numeric types.
fn successor<T>(argument: T)
where T: Add
{
argument + 1
}
Run forc build, and you will get:
.. |
9 | where T: Add
10 | {
11 | argument + 1
| ^ Mismatched types: expected type "T" but saw type "u64"
12 | }
13 |
This is because we don't know for a fact that 1, which in this case defaulted to 1u64, actually can be added to T. What if T is f64? Or b256? What does it mean to add 1u64 in these cases?
We can solve this problem with another trait constraint. We can only find the successor of some value of type T if that type T defines some incrementor. Let's make a trait:
trait Incrementable {
/// Returns the value to add when calculating the successor of a value.
fn incrementor() -> Self;
}
Now, we can modify our successor() function:
fn successor<T>(argument: T)
where T: Add,
T: Incrementable
{
argument + T::incrementor()
}
Generic Structs and Enums
Just like functions, structs and enums can be generic. Let's take a look at the standard library version of Option<T>:
enum Option<T> {
Some: T,
None: (),
}
Just like an unconstrained generic function, this type exists for all (∀) types T. Result<T, E> is another example:
enum Result<T, E> {
Ok: T,
Err: E,
}
Both generic enums and generic structs can be trait constrained, as well. Consider this struct:
struct Foo<T>
where T: Add
{
field_one: T,
}
Type Arguments
Similar to Rust, Sway has what is colloquially known as the turbofish. The turbofish looks like this: ::<> (see the little fish with bubbles behind it?). The turbofish is used to annotate types in a generic context. Say you have the following function:
fn foo<T, E>(t: T) -> Result<T, E> {
Ok(t)
}
In this code example, which is admittedly asinine, you can't possibly know what type E is. You'd need to provide the type manually, with a turbofish:
fn foo<T, E>(t: T) -> Result<T, E> {
Ok::<T, MyErrorType>(t)
}
It is also common to see the turbofish used on the function itself:
fn main() {
foo::<Bar, Baz>()
}
Traits
Declaring a Trait
A trait opts a type into a certain type of behavior or functionality that can be shared among types. This allows for easy reuse of code and generic programming. If you have ever used a typeclass in Haskell, a trait in Rust, or even an interface in Java, these are similar concepts.
Let's take a look at some code:
trait Compare {
fn equals(self, b: Self) -> bool;
} {
fn not_equals(self, b: Self) -> bool {
!self.equals(b)
}
}
We have just declared a trait called Compare. After the name of the trait, there are two blocks of code (a block is code enclosed in { curly brackets }). The first block is the interface surface. The second block is the methods provided by the trait. If a type can provide the methods in the interface surface, then it gets access to the methods in the trait for free! What the above trait is saying is: if you can determine if two values are equal, then for free, you can determine that they are not equal. Note that trait methods have access to the methods defined in the interface surface.
Implementing a Trait
The example below implements a Compare trait for u64 to check if two numbers are equal. Let's take a look at how that is done:
impl Compare for u64 {
fn equals(self, b: Self) -> bool {
self == b
}
}
The above snippet declares all of the methods in the trait Compare for the type u64. Now, we have access to both the equals and not_equals methods for u64, as long as the trait Compare is in scope.
Supertraits
When using multiple traits, scenarios often come up where one trait may require functionality from another trait. This is where supertraits come in as they allow you to require a trait when implementing another trait, i.e., a trait with a trait.
A good example of this is the Ord trait of the std library of Sway. The Ord trait requires the Eq trait, so Eq is kept as a separate trait as one may decide to implement Eq
without implementing other parts of the Ord trait.
trait Eq {
fn equals(self, b: Self) -> bool;
}
trait Ord: Eq {
fn gte(self, b: Self) -> bool;
}
impl Ord for u64 {
fn gte(self, b: Self) -> bool {
// As `Eq` is a supertrait of `Ord`, `Ord` can access the equals method
self.equals(b) || self.gt(b)
}
}
To require a supertrait, add a : after the trait name and then list the traits you would like to require and separate them with a +.
ABI supertraits
ABIs can also have supertrait annotations:
contract;
struct Foo {}
impl ABIsupertrait for Foo {
fn foo() {}
}
trait ABIsupertrait {
fn foo();
}
abi MyAbi : ABIsupertrait {
fn bar();
} {
fn baz() {
Self::foo() // supertrait method usage
}
}
impl ABIsupertrait for Contract {
fn foo() {}
}
// The implementation of MyAbi for Contract must also implement ABIsupertrait
impl MyAbi for Contract {
fn bar() {
Self::foo() // supertrait method usage
}
}
The implementation of MyAbi for Contract must also implement the ABIsupertrait trait. Methods in ABIsupertrait are not available externally, i.e. they're not actually contract methods, but they can be used in the actual contract methods, as shown in the example above.
ABI supertraits are intended to make contract implementations compositional, allowing combining orthogonal contract features using, for instance, libraries.
SuperABIs
In addition to supertraits, ABIs can have superABI annotations:
contract;
abi MySuperAbi {
fn foo();
}
abi MyAbi : MySuperAbi {
fn bar();
}
impl MySuperAbi for Contract {
fn foo() {}
}
// The implementation of MyAbi for Contract must also implement MySuperAbi
impl MyAbi for Contract {
fn bar() {}
}
The implementation of MyAbi for Contract must also implement the MySuperAbi superABI. Methods in MySuperAbi will be part of the MyAbi contract interface, i.e. will be available externally (and hence cannot be called from other MyAbi contract methods).
SuperABIs are intended to make contract implementations compositional, allowing combining orthogonal contract features using, for instance, libraries.
Associated Items
Traits can declare different kinds of associated items in their interface surface:
Associated functions
Associated functions in traits consist of just function signatures. This indicates that each implementation of the trait for a given type must define all the trait functions.
trait Trait {
fn associated_fn(self, b: Self) -> bool;
}
Associated constants
Associated constants are constants associated with a type.
trait Trait {
const ID: u32 = 0;
}
The initializer expression of an associated constants in a trait definition may be omitted to indicate that each implementation of the trait for a given type must specify an initializer:
trait Trait {
const ID: u32;
}
Check the associated consts section on constants page.
Associated types
Associated types in Sway allow you to define placeholder types within a trait, which can be customized by concrete implementations of that trait. These associated types are used to specify the return types of trait methods or to define type relationships within the trait.
trait MyTrait {
type AssociatedType;
}
Check the associated types section on associated types page.
Trait Constraints
When writing generic code, you can constraint the choice of types for a generic argument by using the where keyword. The where keyword specifies which traits the concrete generic parameter must implement. In the below example, the function expects_some_trait can be called only if the parameter t is of a type that has SomeTrait implemented. To call the expects_both_traits, parameter t must be of a type that implements both SomeTrait and SomeOtherTrait.
trait SomeTrait { }
trait SomeOtherTrait { }
fn expects_some_trait<T>(t: T) where T: SomeTrait {
// ...
}
fn expects_some_other_trait<T>(t: T) where T: SomeOtherTrait {
// ...
}
fn expects_both_traits<T>(t: T) where T: SomeTrait + SomeOtherTrait {
// ...
}
Marker Traits
Sway types can be classified in various ways according to their intrinsic properties. These classifications are represented as marker traits. Marker traits are implemented by the compiler and cannot be explicitly implemented in code.
E.g., all types whose instances can be used in the panic expression automatically implement the Error marker trait. We can use that trait, e.g., to specify that a generic argument must be compatible with the panic expression:
fn panic_with_error<E>(err: E) where E: Error {
panic err;
}
Note
panicexpression and error types have not yet been implemented
All marker traits are defined in the std::marker module.
Use Cases
Custom Types (structs, enums)
Often, libraries and APIs have interfaces that are abstracted over a type that implements a certain trait. It is up to the consumer of the interface to implement that trait for the type they wish to use with the interface. For example, let's take a look at a trait and an interface built off of it.
library;
pub enum Suit {
Hearts: (),
Diamonds: (),
Clubs: (),
Spades: (),
}
pub trait Card {
fn suit(self) -> Suit;
fn value(self) -> u8;
}
fn play_game_with_deck<T>(a: Vec<T>) where T: Card {
// insert some creative card game here
}
Now, if you want to use the function play_game_with_deck with your struct, you must implement Card for your struct. Note that the following code example assumes a dependency games has been included in the Forc.toml file.
script;
use games::*;
struct MyCard {
suit: Suit,
value: u8
}
impl Card for MyCard {
fn suit(self) -> Suit {
self.suit
}
fn value(self) -> u8 {
self.value
}
}
fn main() {
let mut i = 52;
let mut deck: Vec<MyCard> = Vec::with_capacity(50);
while i > 0 {
i = i - 1;
deck.push(MyCard { suit: generate_random_suit(), value: i % 4}
}
play_game_with_deck(deck);
}
fn generate_random_suit() -> Suit {
[ ... ]
}
Associated Types
Associated types in Sway allow you to define placeholder types within a trait, which can be customized by concrete implementations of that trait. These associated types are used to specify the return types of trait methods or to define type relationships within the trait.
Associated types are a powerful feature of Sway's trait system, enabling generic programming and abstraction over types. They help improve code clarity and maintainability by allowing you to define generic traits without committing to specific types.
Declaring Associated Types
Associated types are declared within a trait using the type keyword. Here's the syntax for declaring an associated type:
trait MyTrait {
type AssociatedType;
}
Implementing Associated Types
Concrete implementations of a trait with associated types must provide a specific type for each associated type defined in the trait. Here's an example of implementing a trait with an associated type:
struct MyStruct;
impl MyTrait for MyStruct {
type AssociatedType = u32; // Implementing the associated type with u32
}
In this example, MyStruct implements MyTrait and specifies that the associated type AssociatedType is u32.
Using Associated Types
Associated types are used within trait methods or where the trait is used as a bound for generic functions or structs. You can use the associated type like any other type. Here's an example:
trait MyTrait {
type AssociatedType;
fn get_value(self) -> Self::AssociatedType;
}
struct MyStruct;
impl MyTrait for MyStruct {
type AssociatedType = u32;
fn get_value(self) -> Self::AssociatedType {
42
}
}
In this example, get_value is a trait method that returns an associated type AssociatedType.
Use Cases
Associated types are particularly useful in scenarios where you want to define traits that work with different types of data structures or abstractions, allowing the implementer to specify the concrete types. Some common use cases include:
- Collections: Traits for generic collections that allow users to specify the type of elements.
- Iterator Patterns: Traits for implementing iterators with varying element types.
- Serialization and Deserialization: Traits for serializing and deserializing data with different data formats.
Generics and Trait Constraints
Generics as Constraints
At a high level, Sway allows you to define constraints, or restrictions, that allow you to strike a balance between writing abstract and reusable code and enforcing compile-time checks to determine if the abstract code that you've written is correct.
The "abstract and reusable" part largely comes from generic types and the "enforcing compile-time checks" part largely comes from trait constraints. Generic types can be used with functions, structs, and enums (as we have seen in this book), but they can also be used with traits.
Generic Traits
Combining generic types with traits allows you to write abstract and reusable traits that can be implemented for any number of data types.
For example, imagine that you want to write a trait for converting between
different types. This would be similar to Rust's Into and From traits. In
Sway your conversion trait would look something like:
library;
// ANCHOR: trait_definition
trait Convert<T> {
fn from(t: T) -> Self;
}
// ANCHOR_END: trait_definition
// ANCHOR: trait_impl
struct Square {
width: u64,
}
struct Rectangle {
width: u64,
length: u64,
}
impl Convert<Square> for Rectangle {
fn from(t: Square) -> Self {
Self {
width: t.width,
length: t.width,
}
}
}
// ANCHOR_END: trait_impl
// ANCHOR: trait_usage
fn main() {
let s = Square { width: 5 };
let r = Rectangle::from(s);
}
// ANCHOR_END: trait_usage
// ANCHOR: trait_constraint
fn into_rectangle<T>(t: T) -> Rectangle
where
Rectangle: Convert<T>,
{
Rectangle::from(t)
}
// ANCHOR_END: trait_constraint
The trait Convert takes a generic type T. Convert has one method
from, which takes one parameter of type T and returns a Self. This means
that when you implement Convert for a data type, from will return the type
of that data type but will take as input the type that you define as T. Here
is an example:
library;
// ANCHOR: trait_definition
trait Convert<T> {
fn from(t: T) -> Self;
}
// ANCHOR_END: trait_definition
// ANCHOR: trait_impl
struct Square {
width: u64,
}
struct Rectangle {
width: u64,
length: u64,
}
impl Convert<Square> for Rectangle {
fn from(t: Square) -> Self {
Self {
width: t.width,
length: t.width,
}
}
}
// ANCHOR_END: trait_impl
// ANCHOR: trait_usage
fn main() {
let s = Square { width: 5 };
let r = Rectangle::from(s);
}
// ANCHOR_END: trait_usage
// ANCHOR: trait_constraint
fn into_rectangle<T>(t: T) -> Rectangle
where
Rectangle: Convert<T>,
{
Rectangle::from(t)
}
// ANCHOR_END: trait_constraint
In this example, you have two different data types, Square and Rectangle.
You know that all squares are rectangles and thus Square can convert into Rectangle (but not vice
versa) and thus you can implement the conversion trait for those types.
If we want to call these methods we can do so by:
library;
// ANCHOR: trait_definition
trait Convert<T> {
fn from(t: T) -> Self;
}
// ANCHOR_END: trait_definition
// ANCHOR: trait_impl
struct Square {
width: u64,
}
struct Rectangle {
width: u64,
length: u64,
}
impl Convert<Square> for Rectangle {
fn from(t: Square) -> Self {
Self {
width: t.width,
length: t.width,
}
}
}
// ANCHOR_END: trait_impl
// ANCHOR: trait_usage
fn main() {
let s = Square { width: 5 };
let r = Rectangle::from(s);
}
// ANCHOR_END: trait_usage
// ANCHOR: trait_constraint
fn into_rectangle<T>(t: T) -> Rectangle
where
Rectangle: Convert<T>,
{
Rectangle::from(t)
}
// ANCHOR_END: trait_constraint
Trait Constraints
Trait constraints allow you to use generic types and traits to place constraints on what abstract code you are willing to accept in your program as correct. These constraints take the form of compile-time checks for correctness.
If we wanted to use trait constraints with our Convert trait from the previous
section we could do so like so:
library;
// ANCHOR: trait_definition
trait Convert<T> {
fn from(t: T) -> Self;
}
// ANCHOR_END: trait_definition
// ANCHOR: trait_impl
struct Square {
width: u64,
}
struct Rectangle {
width: u64,
length: u64,
}
impl Convert<Square> for Rectangle {
fn from(t: Square) -> Self {
Self {
width: t.width,
length: t.width,
}
}
}
// ANCHOR_END: trait_impl
// ANCHOR: trait_usage
fn main() {
let s = Square { width: 5 };
let r = Rectangle::from(s);
}
// ANCHOR_END: trait_usage
// ANCHOR: trait_constraint
fn into_rectangle<T>(t: T) -> Rectangle
where
Rectangle: Convert<T>,
{
Rectangle::from(t)
}
// ANCHOR_END: trait_constraint
This function allows you to take any generic data type T and convert it to the
type Rectangle as long as Convert<T> is implemented for Rectangle.
Calling this function with a type T for which Convert<T> is not implemented
for Rectangle will fail Sway's compile-time checks.
Inline Assembly in Sway
While many users will never have to touch assembly language while writing Sway code, it is a powerful tool that enables many advanced use-cases (e.g., optimizations, building libraries, etc).
ASM Block
In Sway, the way we use assembly inline is to declare an asm block like this:
asm() {...}
Declaring an asm block is similar to declaring a function.
We can specify register names to operate on as arguments, we can perform assembly instructions within the block, and we can return a value by specifying a return register.
Here's an example showing what this might look like:
pub fn add_1(num: u32) -> u32 {
asm(r1: num, r2) {
add r2 r1 one;
r2: u32
}
}
The return register is specified at the end of the asm block, after all the assembly instructions. It consists of the register name and an optional return type. In the above example, the return register name is r2 and the return type is u32.
If the return type is omitted, it is u64 by default.
The return register itself is optional. If it is not specified, similar to functions, the returned value from the asm block will be unit, ().
An asm block can only return a single register. If you really need to return more than one value, you can modify a tuple. Here's an example showing how you can implement this for (u64, u64):
script;
fn adder(a: u64, b: u64, c: u64) -> (u64, u64) {
let empty_tuple = (0u64, 0u64);
asm(output: empty_tuple, r1: a, r2: b, r3: c, r4, r5) {
add r4 r1 r2; // add a & b and put the result in r4
add r5 r2 r3; // add b & c and put the result in r5
sw output r4 i0; // store the word in r4 in output + 0 words
sw output r5 i1; // store the word in r5 in output + 1 word
output: (u64, u64) // return both values
}
}
fn main() -> bool {
let (first, second) = adder(1, 2, 3);
assert(first == 3);
assert(second == 5);
true
}
Note that this is contrived example meant to demonstrate the syntax; there's absolutely no need to use assembly to add integers!
Note that in the above example:
- we initialized the register
r1with the value ofnum. - we declared a second register
r2(you may choose any register names you want). - we use the
addopcode to addoneto the value ofr1and store it inr2. oneis an example of a "reserved register", of which there are 16 in total. Further reading on this is linked below under "Semantics".- we return
r2and specify the return type as beingu32.
An important note is that the ji and jnei opcodes are not available within an asm block. For those looking to introduce control flow to asm blocks, it is recommended to surround smaller chunks of asm with control flow (if, else, and while).
Helpful Links
For examples of assembly in action, check out the Sway standard library.
For a complete list of all instructions supported in the FuelVM: Instructions.
And to learn more about the FuelVM semantics: Semantics.
Never Type
The Never type ! represents the type of computations which never resolve to any value at all.
Additional Information
break, continue and return expressions also have type !. For example we are allowed to
write:
let x: ! = {
return 123
};
Although the let is pointless here, it illustrates the meaning of !. Since x is never
assigned a value (because return returns from the entire function), x can be given type
Never. We could also replace return 123 with a revert() or a never-ending loop and this code
would still be valid.
A more realistic usage of Never is in this code:
let num: u32 = match get_a_number() {
Some(num) => num,
None => break,
};
Both match arms must produce values of type [u32], but since break never produces a value
at all we know it can never produce a value which isn't a [u32]. This illustrates another
behaviour of the ! type - expressions with type ! will coerce into any other type.
Note that ! type coerces into any other type, another example of this would be:
let x: u32 = {
return 123
};
Regardless of the type of x, the return block of type Never will always coerce into x type.
Examples
fn foo() {
let num: u64 = match Option::None::<u64> {
Some(num) => num,
None => return,
};
}
Common Collections
Sway’s standard library includes a number of very useful data structures called collections. Most other data types represent one specific value, but collections can contain multiple values. Unlike the built-in array and tuple types which are allocated on the "stack" and cannot grow in size, the data these collections point to is stored either on the "heap" or in contract "storage", which means the amount of data does not need to be known at compile time and can grow as the program runs. Each kind of collection has different capabilities and costs, and choosing an appropriate one for your current situation is a skill you’ll develop over time. In this chapter, we’ll discuss three collections that are used very often in Sway programs:
A vector on the heap allows you to store a variable number of values next to each other.
A StorageVec is similar to a vector on the heap but uses persistent storage.
A StorageMap allows you to associate a value with a particular key.
We’ll discuss how to create and update a vector, StorageVec, and StorageMap, as well as what makes each special.
Vectors on the Heap
The first collection type we’ll look at is Vec<T>, also known as a vector. Vectors allow you to store more than one value in a single data structure that puts all the values next to each other in memory. Vectors can only store values of the same type. They are useful when you have a list of items, such as the lines of text in a file or the prices of items in a shopping cart.
Vec<T> is included in the standard library prelude which means that there is no need to import it manually.
Creating a New Vector
To create a new empty vector, we call the Vec::new function, as shown below:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Note that we added a type annotation here. Because we aren’t inserting any values into this vector, the Sway compiler doesn’t know what kind of elements we intend to store. Vectors are implemented using generics which means that the Vec<T> type provided by the standard library can hold any type. When we create a vector to hold a specific type, we can specify the type within angle brackets. In the example above, we’ve told the Sway compiler that the Vec<T> in v will hold elements of the u64 type.
Updating a Vector
To create a vector and then add elements to it, we can use the push method, as shown below:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
As with any variable, if we want to be able to change its value, we need to make it mutable using the mut keyword, as discussed in the section Declaring a Variable. The numbers we place inside are all of type u64, and the Sway compiler infers this from the data, so we don’t need the Vec<u64> annotation.
Reading Elements of Vectors
To read a value stored in a vector at a particular index, you can use the get method as shown below:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Note two details here. First, we use the index value of 2 to get the third element because vectors are indexed by number, starting at zero. Second, we get the third element by using the get method with the index passed as an argument, which gives us an Option<T>.
When the get method is passed an index that is outside the vector, it returns None without panicking. This is particularly useful if accessing an element beyond the range of the vector may happen occasionally under normal circumstances. Your code will then have logic to handle having either Some(element) or None. For example, the index could be coming as a contract method argument. If the argument passed is too large, the method get will return a None value, and the contract method may then decide to revert when that happens or return a meaningful error that tells the user how many items are in the current vector and give them another chance to pass a valid value.
Iterating over the Values in a Vector
To access elements of a vector, we can iterate through the valid indices using a while loop and the len method as shown below:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Note two details here. First, we use the method len which returns the length of the vector. Second, we call the method unwrap to extract the Option returned by get. We know that unwrap will not fail (i.e. will not cause a revert) because each index i passed to get is known to be smaller than the length of the vector.
The idiomatic and convenient way to access each element in a vector in turn, is to use the for loop in the combination with the iter method. The iter method returns an iterator that iterates over all the elements of the vector sequentially.
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Note that modifying a vector during iteration, by e.g. adding or removing elements, is a logical error and results in an undefined behavior:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Accessing vector elements via while loop should be used only when more control over traversal is needed. E.g., in the below example we iterate the vector backwards, accessing only every second element.
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Using an Enum to store Multiple Types
Vectors can only store values that are the same type. This can be inconvenient; there are definitely use cases for needing to store a list of items of different types. Fortunately, the variants of an enum are defined under the same enum type, so when we need one type to represent elements of different types, we can define and use an enum!
For example, say we want to get values from a row in a table in which some of the columns in the row contain integers, some b256 values, and some Booleans. We can define an enum whose variants will hold the different value types, and all the enum variants will be considered the same type: that of the enum. Then we can create a vector to hold that enum and so, ultimately, holds different types. We’ve demonstrated this below:
script;
fn main() {
// ANCHOR: vec_new
let v: Vec<u64> = Vec::new();
// ANCHOR_END: vec_new
// ANCHOR: vec_push
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
// ANCHOR_END: vec_push
// ANCHOR: vec_get
let third = v.get(2);
match third {
Some(third) => log(third),
None => revert(42),
}
// ANCHOR_END: vec_get
// ANCHOR: vec_get_oob
let does_not_exist = v.get(100);
// ...decide here how to handle an out-of-bounds access
// ANCHOR_END: vec_get_oob
// ANCHOR: vec_iterate_while
let mut i = 0;
while i < v.len() {
log(v.get(i).unwrap());
i += 1;
}
// ANCHOR_END: vec_iterate_while
// ANCHOR: vec_iterate_for
for elem in v.iter() {
log(elem);
}
// ANCHOR_END: vec_iterate_for
// ANCHOR: vec_iterate_for_undefined
for elem in v.iter() {
log(elem);
if elem == 3 {
v.push(6); // Modification causes undefined behavior!
}
}
// ANCHOR_END: vec_iterate_for_undefined
// ANCHOR: vec_iterate_custom
// Start from the end
let mut i = v.len() - 1;
while 0 <= i {
log(v.get(i).unwrap());
// Access every second element
i -= 2;
}
// ANCHOR_END: vec_iterate_custom
// ANCHOR: vec_multiple_data_types
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
let mut row = Vec::new();
row.push(TableCell::Int(3));
row.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
row.push(TableCell::Boolean(true));
// ANCHOR_END: vec_multiple_data_types
}
Now that we’ve discussed some of the most common ways to use vectors, be sure to review the API documentation for all the many useful methods defined on Vec<T> by the standard library. For now, these can be found in the source code for Vec<T>. For example, in addition to push, a pop method removes and returns the last element, a remove method removes and returns the element at some chosen index within the vector, an insert method inserts an element at some chosen index within the vector, etc.
Storage Vectors
The second collection type we’ll look at is StorageVec<T>. Just like vectors on the heap (i.e. Vec<T>), storage vectors allow you to store more than one value in a single data structure where each value is assigned an index and can only store values of the same type. However, unlike Vec<T>, the elements of a StorageVec are stored in persistent storage, and consecutive elements are not necessarily stored in storage slots that have consecutive keys.
In order to use StorageVec<T>, you must first import StorageVec as follows:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Another major difference between Vec<T> and StorageVec<T> is that StorageVec<T> can only be used in a contract because only contracts are allowed to access persistent storage.
Creating a New StorageVec
To create a new empty StorageVec, we have to declare the vector in a storage block as follows:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Just like any other storage variable, two things are required when declaring a StorageVec: a type annotation and an initializer. The initializer is just an empty struct of type StorageVec because StorageVec<T> itself is an empty struct! Everything that is interesting about StorageVec<T> is implemented in its methods.
Storage vectors, just like Vec<T>, are implemented using generics which means that the StorageVec<T> type provided by the standard library can hold any type. When we create a StorageVec to hold a specific type, we can specify the type within angle brackets. In the example above, we’ve told the Sway compiler that the StorageVec<T> in v will hold elements of the u64 type.
Updating a StorageVec
To add elements to a StorageVec, we can use the push method, as shown below:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Note two details here. First, in order to use push, we need to first access the vector using the storage keyword. Second, because push requires accessing storage, a storage annotation is required on the ABI function that calls push. While it may seem that #[storage(write)] should be enough here, the read annotation is also required because each call to push requires reading (and then updating) the length of the StorageVec which is also stored in persistent storage.
Note The storage annotation is also required for any private function defined in the contract that tries to push into the vector.
Note There is no need to add the
mutkeyword when declaring aStorageVec<T>. All storage variables are mutable by default.
Reading Elements of Storage Vectors
To read a value stored in a vector at a particular index, you can use the get method as shown below:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Note three details here. First, we use the index value of 2 to get the third element because vectors are indexed by number, starting at zero. Second, we get the third element by using the get method with the index passed as an argument, which gives us an Option<StorageKey<T>>. Third, the ABI function calling get only requires the annotation #[storage(read)] as one might expect because get does not write to storage.
When the get method is passed an index that is outside the vector, it returns None without panicking. This is particularly useful if accessing an element beyond the range of the vector may happen occasionally under normal circumstances. Your code will then have logic to handle having either Some(element) or None. For example, the index could be coming as a contract method argument. If the argument passed is too large, the method get will return a None value, and the contract method may then decide to revert when that happens or return a meaningful error that tells the user how many items are in the current vector and give them another chance to pass a valid value.
Iterating over the Values in a Vector
Iterating over a storage vector is conceptually the same as iterating over a Vec<T>. The only difference is an additional call to read() to actually read the stored value.
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Note that modifying a vector during iteration, by e.g. adding or removing elements, is a logical error and results in an undefined behavior:
Using an Enum to store Multiple Types
Storage vectors, just like Vec<T>, can only store values that are the same type. Similarly to what we did for Vec<T> in the section Using an Enum to store Multiple Types, we can define an enum whose variants will hold the different value types, and all the enum variants will be considered the same type: that of the enum. This is shown below:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Then we can declare a StorageVec in a storage block to hold that enum and so, ultimately, holds different types:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
We can now push different enum variants to the StorageVec as follows:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Now that we’ve discussed some of the most common ways to use storage vectors, be sure to review the API documentation for all the many useful methods defined on StorageVec<T> by the standard library. For now, these can be found in the source code for StorageVec<T>. For example, in addition to push, a pop method removes and returns the last element, a remove method removes and returns the element at some chosen index within the vector, an insert method inserts an element at some chosen index within the vector, etc.
Nested Storage Vectors
It is possible to nest storage vectors as follows:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
The nested vector can then be accessed as follows:
contract;
// ANCHOR: storage_vec_import
use std::storage::storage_vec::*;
// ANCHOR_END: storage_vec_import
// ANCHOR: storage_vec_multiple_types_enum
enum TableCell {
Int: u64,
B256: b256,
Boolean: bool,
}
// ANCHOR_END: storage_vec_multiple_types_enum
storage {
// ANCHOR: storage_vec_decl
v: StorageVec<u64> = StorageVec {},
// ANCHOR_END: storage_vec_decl
// ANCHOR: storage_vec_multiple_types_decl
row: StorageVec<TableCell> = StorageVec {},
// ANCHOR_END: storage_vec_multiple_types_decl
// ANCHOR: storage_vec_nested
nested_vec: StorageVec<StorageVec<u64>> = StorageVec {},
// ANCHOR_END: storage_vec_nested
}
abi StorageVecContract {
#[storage(read, write)]
fn push_to_storage_vec();
#[storage(read)]
fn read_from_storage_vec();
#[storage(read)]
fn iterate_over_a_storage_vec();
#[storage(read, write)]
fn push_to_multiple_types_storage_vec();
#[storage(read, write)]
fn access_nested_vec();
}
impl StorageVecContract for Contract {
// ANCHOR: storage_vec_push
#[storage(read, write)]
fn push_to_storage_vec() {
storage.v.push(5);
storage.v.push(6);
storage.v.push(7);
storage.v.push(8);
}
// ANCHOR_END: storage_vec_push
// ANCHOR: storage_vec_get
#[storage(read)]
fn read_from_storage_vec() {
let third = storage.v.get(2);
match third {
Some(third) => log(third.read()),
None => revert(42),
}
}
// ANCHOR_END: storage_vec_get
// ANCHOR: storage_vec_iterate
#[storage(read)]
fn iterate_over_a_storage_vec() {
// Iterate over all the elements
// in turn using the `while` loop.
// **This approach is not recommended.**
// For iterating over all the elements
// in turn use the `for` loop instead.
let mut i = 0;
while i < storage.v.len() {
log(storage.v.get(i).unwrap().read());
i += 1;
}
// The preferred and most performant way
// to iterate over all the elements in turn is
// to use the `for` loop.
for elem in storage.v.iter() {
log(elem.read());
}
// Use the `while` loop only when more
// control over traversal is needed.
// E.g., in the below example we iterate
// the vector backwards, accessing only
// every second element.
let mut i = storage.v.len() - 1;
while 0 <= i {
log(storage.v.get(i).unwrap().read());
i -= 2;
}
}
// ANCHOR_END: storage_vec_iterate
// ANCHOR: storage_vec_multiple_types_fn
#[storage(read, write)]
fn push_to_multiple_types_storage_vec() {
storage.row.push(TableCell::Int(3));
storage
.row
.push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101));
storage.row.push(TableCell::Boolean(true));
}
// ANCHOR_END: storage_vec_multiple_types_fn
// ANCHOR: access_nested_vec
#[storage(read, write)]
fn access_nested_vec() {
storage.nested_vec.push(StorageVec {});
storage.nested_vec.push(StorageVec {});
let mut inner_vec0 = storage.nested_vec.get(0).unwrap();
let mut inner_vec1 = storage.nested_vec.get(1).unwrap();
inner_vec0.push(0);
inner_vec0.push(1);
inner_vec1.push(2);
inner_vec1.push(3);
inner_vec1.push(4);
assert(inner_vec0.len() == 2);
assert(inner_vec0.get(0).unwrap().read() == 0);
assert(inner_vec0.get(1).unwrap().read() == 1);
assert(inner_vec0.get(2).is_none());
assert(inner_vec1.len() == 3);
assert(inner_vec1.get(0).unwrap().read() == 2);
assert(inner_vec1.get(1).unwrap().read() == 3);
assert(inner_vec1.get(2).unwrap().read() == 4);
assert(inner_vec1.get(3).is_none());
}
// ANCHOR_END: access_nested_vec
}
Storage Maps
Another important common collection is the storage map.
The type StorageMap<K, V> from the standard library stores a mapping of keys of type K to values of type V using a hashing function, which determines how it places these keys and values into storage slots. This is similar to Rust's HashMap<K, V> but with a few differences.
Storage maps are useful when you want to look up data not by using an index, as you can with vectors, but by using a key that can be of any type. For example, when building a ledger-based sub-currency smart contract, you could keep track of the balance of each wallet in a storage map in which each key is a wallet’s Address and the values are each wallet’s balance. Given an Address, you can retrieve its balance.
Similarly to StorageVec<T>, StorageMap<K, V> can only be used in a contract because only contracts are allowed to access persistent storage.
StorageMap<T> is included in the standard library prelude which means that there is no need to import it manually.
Creating a New Storage Map
To create a new empty storage map, we have to declare the map in a storage block as follows:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
Just like any other storage variable, two things are required when declaring a StorageMap: a type annotation and an initializer. The initializer is just an empty struct of type StorageMap because StorageMap<K, V> itself is an empty struct! Everything that is interesting about StorageMap<K, V> is implemented in its methods.
Storage maps, just like Vec<T> and StorageVec<T>, are implemented using generics which means that the StorageMap<K, V> type provided by the standard library can map keys of any type K to values of any type V. In the example above, we’ve told the Sway compiler that the StorageMap<K, V> in map will map keys of type Address to values of type u64.
Updating a Storage Map
To insert key-value pairs into a storage map, we can use the insert method.
For example:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
Note two details here. First, in order to use insert, we need to first access the storage map using the storage keyword. Second, because insert requires writing into storage, a #[storage(write)] annotation is required on the ABI function that calls insert.
Note The storage annotation is also required for any private function defined in the contract that tries to insert into the map.
Note There is no need to add the
mutkeyword when declaring aStorageMap<K, V>. All storage variables are mutable by default.
Accessing Values in a Storage Map
We can get a value out of the storage map by providing its key to the get method.
For example:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
Here, value1 will have the value that's associated with the first address, and the result will be 42. The get method returns an Option<V>; if there’s no value for that key in the storage map, get will return None. This program handles the Option by calling unwrap_or to set value1 to zero if map doesn't have an entry for the key.
Storage Maps with Multiple Keys
Maps with multiple keys can be implemented using tuples as keys. For example:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
Nested Storage Maps
It is possible to nest storage maps as follows:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
The nested map can then be accessed as follows:
contract;
use std::hash::*;
storage {
// ANCHOR: storage_map_decl
map: StorageMap<Address, u64> = StorageMap::<Address, u64> {},
// ANCHOR_END: storage_map_decl
// ANCHOR: storage_map_tuple_key
map_two_keys: StorageMap<(b256, bool), b256> = StorageMap::<(b256, bool), b256> {},
// ANCHOR_END: storage_map_tuple_key
// ANCHOR: storage_map_nested
nested_map: StorageMap<u64, StorageMap<u64, u64>> = StorageMap::<u64, StorageMap<u64, u64>> {},
// ANCHOR_END: storage_map_nested
}
abi StorageMapExample {
#[storage(write)]
fn insert_into_storage_map();
#[storage(read, write)]
fn get_from_storage_map();
#[storage(read, write)]
fn access_nested_map();
}
impl StorageMapExample for Contract {
// ANCHOR: storage_map_insert
#[storage(write)]
fn insert_into_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
}
// ANCHOR_END: storage_map_insert
// ANCHOR: storage_map_get
#[storage(read, write)]
fn get_from_storage_map() {
let addr1 = Address::from(0x0101010101010101010101010101010101010101010101010101010101010101);
let addr2 = Address::from(0x0202020202020202020202020202020202020202020202020202020202020202);
storage.map.insert(addr1, 42);
storage.map.insert(addr2, 77);
let value1 = storage.map.get(addr1).try_read().unwrap_or(0);
}
// ANCHOR_END: storage_map_get
// ANCHOR: storage_map_nested_access
#[storage(read, write)]
fn access_nested_map() {
storage.nested_map.get(0).insert(1, 42);
storage.nested_map.get(2).insert(3, 24);
assert(storage.nested_map.get(0).get(1).read() == 42);
assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here
assert(storage.nested_map.get(2).get(3).read() == 24);
assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here
}
// ANCHOR_END: storage_map_nested_access
}
Testing
Sway aims to provide facilities for both unit testing and integration testing.
Unit testing refers to "in-language" test functions annotated with #[test].
Integration testing refers to the testing of your Sway project's integration within some wider application. You can add integration testing to your Sway+Rust projects today using the cargo generate template and Rust SDK.
Unit Testing
Forc provides built-in support for building and executing tests for a package.
Tests are written as free functions with the #[test] attribute.
For example:
#[test]
fn test_meaning_of_life() {
assert(6 * 7 == 42);
}
Each test function is ran as if it were the entry point for a script. Tests "pass" if they return successfully, and "fail" if they revert or vice versa while testing failure.
If the project has failing tests forc test will exit with exit status 101.
Building and Running Tests
We can build and execute all tests within a package with the following:
forc test
The output should look similar to this:
Compiled library "std".
Compiled library "lib_single_test".
Bytecode size is 92 bytes.
Running 1 tests
test test_meaning_of_life ... ok (170.652µs)
Result: OK. 1 passed. 0 failed. Finished in 1.564996ms.
Visit the forc test command reference to find
the options available for forc test.
Testing Failure
Forc supports testing failing cases for test functions declared with #[test(should_revert)].
For example:
#[test(should_revert)]
fn test_meaning_of_life() {
assert(6 * 6 == 42);
}
It is also possible to specify an expected revert code, like the following example.
#[test(should_revert = "18446744073709486084")]
fn test_meaning_of_life() {
assert(6 * 6 == 42);
}
Tests with #[test(should_revert)] are considered to be passing if they are reverting.
Calling Contracts
Unit tests can call contract functions an example for such calls can be seen below.
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}
To test the test_function(), a unit test like the following can be written.
#[test]
fn test_success() {
let caller = abi(MyContract, CONTRACT_ID);
let result = caller.test_function {}();
assert(result == true)
}
It is also possible to test failure with contract calls as well.
#[test(should_revert)]
fn test_fail() {
let caller = abi(MyContract, CONTRACT_ID);
let result = caller.test_function {}();
assert(result == false)
}
Note: When running
forc test, your contract will be built twice: first without unit tests in order to determine the contract's ID, then a second time with unit tests with theCONTRACT_IDprovided to their namespace. ThisCONTRACT_IDcan be used with theabicast to enable contract calls within unit tests.
Unit tests can call methods of external contracts if those contracts are added as contract dependencies, i.e. in the contract-dependencies section of the manifest file. An example of such calls is shown below:
// ANCHOR: multi_contract_calls
contract;
abi CallerContract {
fn test_false() -> bool;
}
impl CallerContract for Contract {
fn test_false() -> bool {
false
}
}
abi CalleeContract {
fn test_true() -> bool;
}
#[test]
fn test_multi_contract_calls() {
let caller = abi(CallerContract, CONTRACT_ID);
let callee = abi(CalleeContract, callee::CONTRACT_ID);
let should_be_false = caller.test_false();
let should_be_true = callee.test_true();
assert(!should_be_false);
assert(should_be_true);
}
// ANCHOR: multi_contract_calls
Example Forc.toml for contract above:
# ANCHOR: multi_contract_call_toml
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "caller"
[dependencies]
std = { path = "../../../sway-lib-std/" }
[contract-dependencies]
callee = { path = "../callee" }
# ANCHOR: multi_contract_call_toml
Running Tests in Parallel or Serially
By default, all unit tests in your project are run in parallel. Note that this does not lead to any data races in storage because each unit test has its own storage space that is not shared by any other unit test.
By default, forc test will use all the available threads in your system. To request that a specific number of threads be used, the flag --test-threads <val> can be provided to forc test.
forc test --test-threads 1
Logs Inside Tests
Forc has some capacity to help decode logs returned from the unit tests. You can use this feature to decode raw logs into a human readable format.
script;
fn main() {}
#[test]
fn test_fn() {
let a = 10;
log(a);
let b = 30;
log(b);
assert_eq(a, 10)
assert_eq(b, 30)
}
The example shown above is logging two different variables, a and b and their values are 10 and 30, respectively. Without log decoding printed log for this test with forc test --logs (--logs flag is required to see the logs for this example since the test is passing. Logs are silenced by default in passing tests, and can be enabled using the --logs flag.):
Finished debug [unoptimized + fuel] target(s) in 5.23s
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
Running 1 test, filtered 0 tests
test test_fn ... ok (38.875µs, 232 gas)
[{"LogData":{"data":"000000000000000a","digest":"8d85f8467240628a94819b26bee26e3a9b2804334c63482deacec8d64ab4e1e7","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11032,"ptr":67107840,"ra":0,"rb":0}},{"LogData":{"data":"000000000000001e","digest":"48a97e421546f8d4cae1cf88c51a459a8c10a88442eed63643dd263cef880c1c","id":"0000000000000000000000000000000000000000000000000000000000000000","is":10368,"len":8,"pc":11516,"ptr":67106816,"ra":0,"rb":1}}]
This is not very easy to understand, it is possible to decode these logs with --decode flag, executing forc test --logs --decode:
Finished debug [unoptimized + fuel] target(s) in 5.23s
Bytecode hash: 0x1cb1edc031691c5c08b50fd0f07b02431848ab81b325b72eb3fd233c67d6b548
Running 1 test, filtered 0 tests
test test_fn ... ok (38.875µs, 232 gas)
Decoded log value: 10, log rb: 0
Decoded log value: 30, log rb: 1
As it can be seen, the values are human readable and easier to understand which makes debugging much more easier.
Note: This is an experimental feature and we are actively working on reporting variable names next to their values.
Testing with Rust
A common use of Sway is for writing contracts or scripts that exist as part of a wider Rust application. In order to test the interaction between our Sway code and our Rust code we can add integration testing.
Adding Rust Integration Testing
To add Rust integration testing to a Forc project we can use the sway-test-rs
cargo generate
template.
This template makes it easier for Sway developers to add the boilerplate required when
setting up their Rust integration testing.
Let's add a Rust integration test to the fresh project we created in the introduction.
1. Enter the project
To recap, here's what our empty project looks like:
$ cd my-fuel-project
$ tree .
├── Forc.toml
└── src
└── main.sw
2. Install cargo generate
We're going to add a Rust integration test harness using a cargo generate
template. Let's make sure we have the cargo generate command installed!
cargo install cargo-generate
Note: You can learn more about cargo generate by visiting the cargo-generate repository.
3. Generate the test harness
Let's generate the default test harness with the following:
cargo generate --init fuellabs/sway templates/sway-test-rs --name my-fuel-project --force
--force forces your --name input to retain your desired casing for the {{project-name}}
placeholder in the template. Otherwise, cargo-generate automatically converts it to kebab-case.
With --force, this means that both my_fuel_project and my-fuel-project are valid project names,
depending on your needs.
_Note:
templates/sway-test-rscan be replaced withtemplates/sway-script-test-rsortemplates/sway-predicate-test-rsto generate a test harness for scripts and predicates respectively.
If all goes well, the output should look as follows:
⚠️ Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway
🤷 Project Name : my-fuel-project
🔧 Destination: /home/user/path/to/my-fuel-project ...
🔧 Generating template ...
[1/3] Done: Cargo.toml
[2/3] Done: tests/harness.rs
[3/3] Done: tests
🔧 Moving generated files into: `/home/user/path/to/my-fuel-project`...
✨ Done! New project created /home/user/path/to/my-fuel-project
Let's have a look at the result:
$ tree .
├── Cargo.toml
├── Forc.toml
├── build.rs
├── src
│ └── main.sw
└── tests
└── harness.rs
We have three new files!
- The
Cargo.tomlis the manifest for our new test harness and specifies the required dependencies includingfuelsthe Fuel Rust SDK. - The
tests/harness.rscontains some boilerplate test code to get us started, though doesn't call any contract methods just yet. - The
build.rsis a build script that compiles the Sway project withforc buildwhenevercargo testis run.
4. Build the forc project
Before running the tests, we need to build our contract so that the necessary
ABI, storage and bytecode artifacts are available. We can do so with forc build:
$ forc build
Creating a new `Forc.lock` file. (Cause: lock file did not exist)
Adding std git+https://github.com/fuellabs/sway?tag=v0.24.5#e695606d8884a18664f6231681333a784e623bc9
Created new lock file at /home/user/path/to/my-fuel-project/Forc.lock
Compiled library "std".
Compiled contract "my-fuel-project".
Bytecode size is 60 bytes.
At this point, our project should look like the following:
$ tree
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── build.rs
├── out
│ └── debug
│ ├── my-fuel-project-abi.json
│ ├── my-fuel-project.bin
│ └── my-fuel-project-storage_slots.json
├── src
│ └── main.sw
└── tests
└── harness.rs
We now have an out directory with our required JSON files!
Note: This step may no longer be required in the future as we plan to enable the integration testing to automatically build the artifacts as necessary so that files like the ABI JSON are always up to date.
5. Build and run the tests
Now we're ready to build and run the default integration test.
$ cargo test
Updating crates.io index
Compiling version_check v0.9.4
Compiling proc-macro2 v1.0.46
Compiling quote v1.0.21
...
Compiling fuels v0.24.0
Compiling my-fuel-project v0.1.0 (/home/user/path/to/my-fuel-project)
Finished test [unoptimized + debuginfo] target(s) in 1m 03s
Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)
running 1 test
test can_get_contract_id ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.36s
Note: The first time we run
cargo test, cargo will spend some time fetching and building the dependencies for Fuel's Rust SDK. This might take a while, but only the first time!
If all went well, we should see some output that looks like the above!
Writing Tests
Now that we've learned how to setup Rust integration testing in our project, let's try to write some of our own tests!
First, let's update our contract code with a simple counter example:
contract;
abi TestContract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64;
#[storage(read, write)]
fn increment_counter(amount: u64) -> u64;
}
storage {
counter: u64 = 0,
}
impl TestContract for Contract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64 {
storage.counter.write(value);
value
}
#[storage(read, write)]
fn increment_counter(amount: u64) -> u64 {
let incremented = storage.counter.read() + amount;
storage.counter.write(incremented);
incremented
}
}
To test our initialize_counter and increment_counter contract methods from
the Rust test harness, we could update our tests/harness.rs file with the
following:
use fuels::{prelude::*, types::ContractId};
// Load abi from json
abigen!(Contract(
name = "MyContract",
abi = "out/debug/my-fuel-project-abi.json"
));
async fn get_contract_instance() -> (MyContract<WalletUnlocked>, ContractId) {
// Launch a local network and deploy the contract
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(
Some(1), /* Single wallet */
Some(1), /* Single coin (UTXO) */
Some(1_000_000_000), /* Amount per coin */
),
None,
None,
)
.await
.unwrap();
let wallet = wallets.pop().unwrap();
let id = Contract::load_from(
"./out/debug/my-fuel-project.bin",
LoadConfiguration::default().set_storage_configuration(
StorageConfiguration::load_from(
"./out/debug/my-fuel-project-storage_slots.json",
)
.unwrap(),
),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = MyContract::new(id.clone(), wallet);
(instance, id.into())
}
#[tokio::test]
async fn initialize_and_increment() {
let (contract_instance, _id) = get_contract_instance().await;
// Now you have an instance of your contract you can use to test each function
let result = contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
assert_eq!(42, result.value);
// Call `increment_counter()` method in our deployed contract.
let result = contract_instance
.methods()
.increment_counter(10)
.call()
.await
.unwrap();
assert_eq!(52, result.value);
}
Let's build our project once more and run the test:
forc build
$ cargo test
Compiling my-fuel-project v0.1.0 (/home/mindtree/programming/sway/my-fuel-project)
Finished test [unoptimized + debuginfo] target(s) in 11.61s
Running tests/harness.rs (target/debug/deps/integration_tests-373971ac377845f7)
running 1 test
test initialize_and_increment ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.25s
When cargo runs our test, our test uses the SDK to spin up a local in-memory Fuel network, deploy our contract to it, and call the contract methods via the ABI.
You can add as many functions decorated with #[tokio::test] as you like, and
cargo test will automatically test each of them!
Debugging
Forc provides tools for debugging both live transactions as well as Sway unit tests. Debugging can be done via CLI or using the VSCode IDE.
Unit testing refers to "in-language" test functions annotated with #[test]. Line-by-line
debugging is available within the VSCode IDE.
Live transaction refers to the testing sending a transaction to a running Fuel Client
node to exercise your Sway code. Instruction-by-instruction debugging is available in the forc debug CLI.
Debugging with CLI
The forc debug CLI enables debugging a live transaction on a running Fuel Client node.
An example project
First, we need a project to debug, so create a new project using
forc new --script dbg_example && cd dbg_example
And then add some content to src/main.sw, for example:
script;
use std::logging::log;
fn factorial(n: u64) -> u64 {
let mut result = 1;
let mut counter = 0;
while counter < n {
counter = counter + 1;
result = result * counter;
}
return result;
}
fn main() {
log::<u64>(factorial(5)); // 120
}
Building and bytecode output
Now we are ready to build the project.
forc build
After this the resulting binary should be located at out/debug/dbg_example.bin. Because we are interested in the resulting bytecode, we can read that with:
forc parse-bytecode out/debug/dbg_example.bin
We can recognize the main loop by observing the control flow. Looking around halfword 58-60, we can see:
half-word byte op raw
58 232 MOVI { dst: 0x11, val: 5 } 72 44 00 05
59 236 LT { dst: 0x10, lhs: 0x10, rhs: 0x11 } 16 41 04 40
60 240 JNZF { cond_nz: 0x10, dynamic: 0x0, fixed: 81 } 76 40 00 51
Here we can see our factorial(5) being set up with MOVI setting the value 5, followed by the LT comparison and conditional jump JNZF. The multiplication for our factorial happens at halfword 147 with MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 }. Finally, we can spot our log statement at halfword 139 with the LOGD instruction.
Setting up the debugging
We can start up the debug infrastructure. On a new terminal session run fuel-core run --db-type in-memory --debug; we need to have that running because it actually executes the program. Now we can fire up the debugger itself: forc-debug. Now if everything is set up correctly, you should see the debugger prompt (>>). You can use help command to list available commands.
The debugger supports tab completion to help you discover files in your current working directory (and its subdirectories):
- Type
txand press tab to recursively search for valid transaction JSON files - After selecting a transaction file, press tab again to search for ABI files
- You can keep pressing tab to cycle through the found files
- Of course, you can also manually type the full path to any transaction or ABI file, they don't have to be in your current directory
Now we would like to inspect the program while it's running. To do this, we first need to send the script to the executor, i.e. fuel-core. To do so, we need a transaction specification, tx.json. It looks something like this:
{
"Script": {
"body": {
"script_gas_limit": 1000000,
"script": [
26, 240, 48, 0, 116, 0, 0, 2, 0, 0, 0, 0, 0, 0, 3, 96, 93, 255, 192, 1, 16, 255, 255, 0, 26, 236, 80, 0, 145, 0, 0, 184, 80, 67, 176, 80, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 37, 80, 71, 176, 40, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 136, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 79, 176, 120, 114, 68, 0, 24, 40, 79, 180, 64, 80, 71, 176, 160, 114, 72, 0, 24, 40, 69, 52, 128, 80, 71, 176, 96, 114, 72, 0, 24, 40, 69, 52, 128, 80, 75, 176, 64, 26, 233, 16, 0, 26, 229, 32, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 144, 26, 71, 208, 0, 80, 75, 176, 24, 114, 76, 0, 16, 40, 73, 20, 192, 80, 71, 176, 144, 114, 76, 0, 16, 40, 69, 36, 192, 114, 72, 0, 16, 40, 65, 20, 128, 93, 69, 0, 1, 93, 65, 0, 0, 37, 65, 16, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 1, 88, 26, 87, 224, 0, 95, 236, 16, 42, 95, 236, 0, 41, 93, 67, 176, 41, 114, 68, 0, 5, 22, 65, 4, 64, 118, 64, 0, 81, 93, 67, 176, 42, 80, 71, 176, 200, 26, 233, 16, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 87, 26, 71, 208, 0, 114, 72, 0, 24, 40, 237, 20, 128, 80, 71, 176, 160, 114, 72, 0, 24, 40, 71, 180, 128, 80, 75, 176, 24, 114, 76, 0, 24, 40, 73, 20, 192, 80, 71, 176, 88, 114, 76, 0, 24, 40, 69, 36, 192, 93, 83, 176, 11, 93, 79, 176, 12, 93, 71, 176, 13, 114, 72, 0, 8, 16, 73, 20, 128, 21, 73, 36, 192, 118, 72, 0, 1, 116, 0, 0, 7, 114, 72, 0, 2, 27, 73, 52, 128, 114, 76, 0, 8, 16, 77, 36, 192, 38, 76, 0, 0, 40, 29, 68, 64, 26, 80, 112, 0, 16, 73, 68, 64, 95, 73, 0, 0, 114, 64, 0, 8, 16, 65, 20, 0, 80, 71, 176, 112, 95, 237, 64, 14, 95, 237, 48, 15, 95, 237, 0, 16, 80, 67, 176, 48, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 176, 136, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 8, 114, 72, 0, 24, 40, 65, 20, 128, 80, 71, 177, 48, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 177, 48, 80, 71, 176, 240, 114, 72, 0, 24, 40, 69, 4, 128, 80, 67, 176, 224, 26, 233, 16, 0, 26, 229, 0, 0, 32, 248, 51, 0, 88, 251, 224, 2, 80, 251, 224, 4, 116, 0, 0, 56, 26, 67, 208, 0, 80, 71, 176, 72, 114, 72, 0, 16, 40, 69, 4, 128, 80, 67, 177, 32, 114, 72, 0, 16, 40, 65, 20, 128, 80, 71, 176, 184, 114, 72, 0, 16, 40, 69, 4, 128, 93, 67, 240, 0, 93, 71, 176, 23, 93, 75, 176, 24, 52, 1, 4, 82, 26, 244, 0, 0, 116, 0, 0, 8, 93, 67, 176, 41, 16, 65, 0, 64, 95, 237, 0, 41, 93, 67, 176, 42, 93, 71, 176, 41, 27, 65, 4, 64, 95, 237, 0, 42, 117, 0, 0, 91, 146, 0, 1, 88, 26, 249, 80, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 149, 0, 0, 15, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 72, 26, 67, 160, 0, 26, 71, 224, 0, 114, 72, 4, 0, 38, 72, 0, 0, 26, 72, 112, 0, 80, 79, 176, 24, 95, 237, 32, 3, 114, 72, 4, 0, 95, 237, 32, 4, 95, 236, 0, 5, 114, 72, 0, 24, 40, 237, 52, 128, 80, 75, 176, 48, 114, 76, 0, 24, 40, 75, 180, 192, 114, 76, 0, 24, 40, 65, 36, 192, 26, 245, 0, 0, 146, 0, 0, 72, 26, 249, 16, 0, 152, 8, 0, 0, 151, 0, 0, 15, 74, 248, 0, 0, 149, 0, 0, 63, 150, 8, 0, 0, 26, 236, 80, 0, 145, 0, 0, 104, 26, 67, 160, 0, 26, 71, 144, 0, 26, 75, 224, 0, 80, 79, 176, 80, 114, 80, 0, 24, 40, 77, 5, 0, 114, 64, 0, 24, 40, 237, 52, 0, 80, 67, 176, 40, 114, 76, 0, 24, 40, 67, 180, 192, 93, 79, 176, 5, 80, 65, 0, 16, 80, 83, 176, 64, 95, 237, 48, 8, 80, 77, 64, 8, 114, 84, 0, 8, 40, 77, 5, 64, 80, 67, 176, 24, 114, 76, 0, 16, 40, 65, 68, 192, 114, 76, 0, 16, 40, 69, 4, 192, 26, 245, 16, 0, 146, 0, 0, 104, 26, 249, 32, 0, 152, 8, 0, 0, 151, 0, 0, 63, 74, 248, 0, 0, 71, 0, 0, 0, 21, 6, 230, 244, 76, 29, 98, 145
],
"script_data": [],
"receipts_root": "0000000000000000000000000000000000000000000000000000000000000000"
},
"policies": {
"bits": "MaxFee",
"values": [0, 0, 0, 0]
},
"inputs": [
{
"CoinSigned": {
"utxo_id": {
"tx_id": "c49d65de61cf04588a764b557d25cc6c6b4bc0d7429227e2a21e61c213b3a3e2",
"output_index": 18
},
"owner": "f1e92c42b90934aa6372e30bc568a326f6e66a1a0288595e6e3fbd392a4f3e6e",
"amount": 10599410012256088000,
"asset_id": "2cafad611543e0265d89f1c2b60d9ebf5d56ad7e23d9827d6b522fd4d6e44bc3",
"tx_pointer": {
"block_height": 0,
"tx_index": 0
},
"witness_index": 0,
"maturity": 0,
"predicate_gas_used": null,
"predicate": null,
"predicate_data": null
}
}
],
"outputs": [],
"witnesses": [
{
"data": [156, 254, 34, 102, 65, 96, 133, 170, 254, 105, 147, 35, 196, 199, 179, 133, 132, 240, 208, 149, 11, 46, 30, 96, 44, 91, 121, 195, 145, 184, 159, 235, 117, 82, 135, 41, 84, 154, 102, 61, 61, 16, 99, 123, 58, 173, 75, 226, 219, 139, 62, 33, 41, 176, 16, 18, 132, 178, 8, 125, 130, 169, 32, 108]
}
]
}
}
However, the key script should contain the actual bytecode to execute, i.e. the contents of out/debug/dbg_example.bin as a JSON array. The following command can be used to generate it:
python3 -c 'print(list(open("out/debug/dbg_example.bin", "rb").read()))'
So now we replace the script array with the result, and save it as tx.json.
Using the debugger
Now we can actually execute the script with an ABI to decode the log values:
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Receipt: ScriptResult { result: Success, gas_used: 1273 }
Terminated
Looking at the output, we can see our factorial(5) result as the decoded log value of 120. The ABI has helped us decode the raw bytes (0000000000000078) into a meaningful value. It also tells us that the execution terminated without hitting any breakpoints. That's unsurprising, because we haven't set up any. We can do so with breakpoint command:
>> breakpoint 0
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: ScriptResult { result: Success, gas_used: 0 }
Stopped on breakpoint at address 0 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
Now we have stopped execution at the breakpoint on entry (address 0). We can now inspect the initial state of the VM.
>> register ggas
reg[0x9] = 1000000 # ggas
>> memory 0x10 0x8
000010: db f3 63 c9 1c 7f ec 95
However, that's not too interesting either, so let's just execute until the end, and then reset the VM to remove the breakpoints.
>> continue
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Terminated
>> reset
Next, we will setup a breakpoint to check the state on each iteration of the while loop. For instance, if we'd like to see what numbers get multiplied together, we could set up a breakpoint before the operation. Looking at our bytecode we can see the main multiplication for our factorial happens at:
half-word byte op raw
147 588 MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 } 1b 41 04 40
We can set a breakpoint on its address, at halfword-offset 147.
>>> breakpoint 147
>> start_tx tx.json out/debug/dbg_example-abi.json
Receipt: ScriptResult { result: Success, gas_used: 82 }
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
Now we can inspect the inputs to multiply. Looking at the specification tells us that the instruction MUL { dst: 0x10, lhs: 0x10, rhs: 0x11 } means reg[0x10] = reg[0x10] * reg[0x11]. So inspecting the inputs:
>> r 0x10 0x11
reg[0x10] = 1 # reg16
reg[0x11] = 1 # reg17
So on the first round the numbers are 1 and 1, so we can continue to the next iteration with the c command:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 1 # reg16
reg[0x11] = 2 # reg17
And the next one:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 2 # reg16
reg[0x11] = 3 # reg17
And fourth one:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 6 # reg16
reg[0x11] = 4 # reg17
And round 5:
>> c
Stopped on breakpoint at address 588 of contract 0x0000000000000000000000000000000000000000000000000000000000000000
>> r 0x10 0x11
reg[0x10] = 24 # reg16
reg[0x11] = 5 # reg17
At this point we can look at the values
| 0x10 | 0x11 |
|---|---|
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 6 | 4 |
| 24 | 5 |
From this we can clearly see that the left side, register 0x10 is the result variable which accumulates the factorial calculation (1, 1, 2, 6, 24), and register 0x11 is the counter which increments from 1 to 5. Now the counter equals the given factorial function argument 5, and the loop terminates. So when we continue, the program finishes without encountering any more breakpoints:
>> c
Receipt: LogData { id: 0000000000000000000000000000000000000000000000000000000000000000, ra: 0, rb: 1515152261580153489, ptr: 67107840, len: 8, digest: d2b80ebb9ce633ad49a9ccfcc58ac7ad33a9ab4741529ae4247a3b07e8fa1c74, pc: 10924, is: 10368, data: Some(0000000000000078) }
Decoded log value: 120, from contract: 0000000000000000000000000000000000000000000000000000000000000000
Receipt: ReturnData { id: 0000000000000000000000000000000000000000000000000000000000000000, ptr: 67106816, len: 0, digest: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855, pc: 10564, is: 10368, data: Some() }
Terminated
Debugging with IDE
The forc debug plugin also enables line-by-line debugging of Sway unit tests in VSCode.
Installation
- Install the Sway VSCode extension from the marketplace.
- Ensure you have the forc-debug binary installed.
which forc-debug. It can be installed withfuelup component add forc-debug. - Create a
.vscode/launch.jsonfile with the following contents:
{
"version": "0.2.0",
"configurations": [
{
"type": "sway",
"request": "launch",
"name": "Debug Sway",
"program": "${file}"
}]
}
An example project
Given this example contract:
contract;
abi CallerContract {
fn test_false() -> bool;
}
impl CallerContract for Contract {
fn test_false() -> bool {
false
}
}
abi CalleeContract {
fn test_true() -> bool;
}
#[test]
fn test_multi_contract_calls() {
let caller = abi(CallerContract, CONTRACT_ID);
let callee = abi(CalleeContract, callee::CONTRACT_ID);
let should_be_false = caller.test_false();
let should_be_true = callee.test_true();
assert(!should_be_false);
assert(should_be_true);
}
Within the sway file open in VSCode, you can set breakpoints on lines within the test or functions that it calls, and click Run -> Start Debugging to begin debugging the unit test.
This will build the sway project and run it in debug mode. The debugger will stop the VM execution when a breakpoint is hit.
The debug panel will show VM registers under the Variables tab, as well as the current VM opcode where execution is suspended. You can continue execution, or use the Step Over function to step forward, instruction by instruction.
Sway LSP
Welcome to the documentation for Sway LSP, the language server designed specifically for the Sway programming language. This documentation serves as a comprehensive guide to help you understand and utilize the powerful features provided by Sway LSP.
Sway LSP is built on the Language Server Protocol (LSP), a standardized protocol for enabling rich programming language support in editor and IDE environments. It acts as a bridge between your favorite code editor or integrated development environment and the Sway programming language, offering advanced semantic analysis and a wide range of features to enhance your development experience.
With Sway LSP, you can expect a seamless and efficient coding experience while working with the Sway programming language. It provides intelligent code completion, precise symbol navigation, type information, and other smart features that empower you to write clean and error-free code. By leveraging the power of Sway LSP, you can increase productivity, reduce debugging time, and write high-quality code with confidence.
In this documentation, you will find detailed information about how to set up Sway LSP in your preferred code editor or IDE, configure its settings to match your coding style, and take advantage of its various features. We will guide you through the installation process, provide examples of typical configuration setups, and walk you through the usage of each feature supported by Sway LSP.
Whether you are a beginner or an experienced Sway developer, this documentation aims to be your go-to resource for understanding and maximizing the capabilities of Sway LSP. So let's dive in and unlock the full potential of the Sway programming language with Sway LSP!
Installation
The Sway language server is contained in the forc-lsp binary, which is installed as part of the Fuel toolchain. Once installed, it can be used with a variety of IDEs. It must be installed for any of the IDE plugins to work.
Note: There is no need to manually run
forc-lsp(the plugin will automatically start it), however bothforcandforc-lspmust be in your$PATH. To check ifforcis in your$PATH, typeforc --helpin your terminal.
VSCode
This is the best supported editor at the moment.
You can install the latest release of the plugin from the marketplace.
Note that we only support the most recent version of VS Code.
Code OSS (VSCode on Linux)
- Install code-marketplace to get access to all of the extensions in the VSCode marketplace.
- Install the Sway extension.
vim / neovim
Follow the documentation for sway.vim to install.
helix
Install helix and Sway LSP will work out of the box.
Sway support is built into helix using tree-sitter-sway.
Emacs
Coming soon! Feel free to contribute.
Features
Code Actions
Source: code_actions
Quickly generate boilerplate code and code comments for functions, structs, and ABIs.
Completion
Source: completion.rs
Suggests code to follow partially written statements for functions and variables.
Go to Definition
Jumps to the definition of a symbol from its usage.
Find All References
Locates all occurrences of a symbol throughout the project.
Hover
Source: hover
Provides documentation, compiler diagnostics, and reference links when hovering over functions and variables.
Inlay Hints
Source: inlay_hints.rs
Displays the implied type of a variable next to the variable name. Configurable in Settings.
Rename
Source: rename.rs
Renames a symbol everywhere in the workspace.
Diagnostics
Source: diagnostic.rs
Displays compiler warnings and errors inline.
Syntax Highlighting
Source: highlight.rs
Highlights code based on type and context.
Run
Source: runnable.rs
Shows a button above a runnable function or test.
Troubleshooting
First, confirm you are running the most recent version:
fuelup toolchain install latest
fuelup update
forc-lsp --version
Second, confirm that your $PATH resolves to the forc-lsp binary in $HOME/.fuelup/bin.
which forc-lsp
Slow Performance
If you are experiencing slow performance, you can try the following:
Follow the steps above to ensure you are running the most recent version.
Then, make sure you only have the most recent version of the LSP server running.
pkill forc-lsp
Large projects
Sway projects with ten or more Sway files are likely to have slower LSP performance. We are working on better support for large projects.
In the meantime, if it's too slow, you can disable the LSP server entirely with the sway-lsp.diagnostic.disableLsp setting. The extension will still provide basic syntax highlighting, command palettes, as well as the Sway debugger, but all other language features will be disabled.
Server Logs
You can enable verbose logging of the LSP server.
In VSCode, this is under the setting:
"sway-lsp.trace.server": "verbose"
Once enabled, you can find this in the output window under Sway Language Server.
For other editors, see Installation for links to documentation.
Sway Reference
- Compiler Intrinsics
- Attributes
- Style Guide
- Known Issues and Workarounds
- Differences from Rust
- Differences from Solidity
- Contributing to Sway
- Keywords
Sway Libraries
The purpose of Sway Libraries is to contain libraries which users can import and use that are not part of the standard library.
These libraries contain helper functions and other tools valuable to blockchain development.
For more information on how to use a Sway-Libs library, please refer to the Sway-Libs Book.
Assets Libraries
Asset Libraries are any libraries that use Native Assets on the Fuel Network.
- Asset Library; provides helper functions for the SRC-20, SRC-3, and SRC-7 standards.
Access Control and Security Libraries
Access Control and Security Libraries are any libraries that are built and intended to provide additional safety when developing smart contracts.
- Ownership Library; used to apply restrictions on functions such that only a single user may call them. This library provides helper functions for the SRC-5; Ownership Standard.
- Admin Library; used to apply restrictions on functions such that only a select few users may call them like a whitelist.
- Pausable Library; allows contracts to implement an emergency stop mechanism.
- Reentrancy Guard Library; used to detect and prevent reentrancy attacks.
Cryptography Libraries
Cryptography Libraries are any libraries that provided cryptographic functionality beyond what the std-lib provides.
- Bytecode Library; used for on-chain verification and computation of bytecode roots for contracts and predicates.
- Merkle Proof Library; used to verify Binary Merkle Trees computed off-chain.
Math Libraries
Math Libraries are libraries which provide mathematic functions or number types that are outside of the std-lib's scope.
- Signed Integers Library; an interface to implement signed integers.
Data Structures Libraries
Data Structure Libraries are libraries which provide complex data structures which unlock additional functionality for Smart Contracts.
- Queue Library; a linear data structure that provides First-In-First-Out (FIFO) operations.
Compiler Intrinsics
The Sway compiler supports a list of intrinsics that perform various low level operations that are useful for building libraries. Compiler intrinsics should rarely be used but are preferred over asm blocks because they are type-checked and are safer overall. Below is a list of all available compiler intrinsics:
__size_of_val<T>(val: T) -> u64
Description: Return the size of type T in bytes.
Constraints: None.
__size_of<T>() -> u64
Description: Return the size of type T in bytes.
Constraints: None.
__size_of_str_array<T>() -> u64
Description: Return the size of type T in bytes. This intrinsic differs from __size_of in the case of "string arrays" where the actual length in bytes of the string is returned without padding the byte size to the next word alignment. When T is not a "string array" 0 is returned.
Constraints: None.
__assert_is_str_array<T>()
Description: Throws a compile error if type T is not a "string array".
Constraints: None.
__to_str_array(s: str) -> str[N]
Description: Converts a "string slice" to "string array" at compile time. Parameter "s" must be a string literal.
Constraints: None.
__is_reference_type<T>() -> bool
Description: Returns true if T is a reference type and false otherwise.
Constraints: None.
__is_str_array<T>() -> bool
Description: Returns true if T is a string array and false otherwise.
Constraints: None.
__eq<T>(lhs: T, rhs: T) -> bool
Description: Returns whether lhs and rhs are equal.
Constraints: T is bool, u8, u16, u32, u64, u256, b256 or raw_ptr.
__gt<T>(lhs: T, rhs: T) -> bool
Description: Returns whether lhs is greater than rhs.
Constraints: T is u8, u16, u32, u64, u256, b256.
__lt<T>(lhs: T, rhs: T) -> bool
Description: Returns whether lhs is less than rhs.
Constraints: T is u8, u16, u32, u64, u256, b256.
__gtf<T>(index: u64, tx_field_id: u64) -> T
Description: Returns transaction field with ID tx_field_id at index index, if applicable. This is a wrapper around FuelVM's gtf instruction. The resulting field is cast to T.
Constraints: None.
__addr_of<T>(val: T) -> raw_ptr
Description: Returns the address in memory where val is stored.
Constraints: T is a reference type.
__state_load_word(key: b256) -> u64
Description: Reads and returns a single word from storage at key key.
Constraints: None.
__state_load_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool
Description: Reads slots number of slots (b256 each) from storage starting at key key and stores them in memory starting at address ptr. Returns a Boolean describing whether all the storage slots were previously set.
Constraints: None.
__state_store_word(key: b256, val: u64) -> bool
Description: Stores a single word val into storage at key key. Returns a Boolean describing whether the store slot was previously set.
Constraints: None.
__state_store_quad(key: b256, ptr: raw_ptr, slots: u64) -> bool
Description: Stores slots number of slots (b256 each) starting at address ptr in memory into storage starting at key key. Returns a Boolean describing whether the first storage slot was previously set.
Constraints: None.
__log<T>(val: T)
Description: Logs value val.
Constraints: None.
__add<T>(lhs: T, rhs: T) -> T
Description: Adds lhs and rhs and returns the result.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256.
__sub<T>(lhs: T, rhs: T) -> T
Description: Subtracts rhs from lhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256.
__mul<T>(lhs: T, rhs: T) -> T
Description: Multiplies lhs by rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256.
__div<T>(lhs: T, rhs: T) -> T
Description: Divides lhs by rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256.
__and<T>(lhs: T, rhs: T) -> T
Description: Bitwise AND lhs and rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__or<T>(lhs: T, rhs: T) -> T
Description: Bitwise OR lhs and rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__xor<T>(lhs: T, rhs: T) -> T
Description: Bitwise XOR lhs and rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__mod<T>(lhs: T, rhs: T) -> T
Description: Modulo of lhs by rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256.
__rsh<T>(lhs: T, rhs: u64) -> T
Description: Logical right shift of lhs by rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__lsh<T>(lhs: T, rhs: u64) -> T
Description: Logical left shift of lhs by rhs.
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__revert(code: u64)
Description: Reverts with error code code.
Constraints: None.
__ptr_add(ptr: raw_ptr, offset: u64)
Description: Adds offset to the raw value of pointer ptr.
Constraints: None.
__ptr_sub(ptr: raw_ptr, offset: u64)
Description: Subtracts offset to the raw value of pointer ptr.
Constraints: None.
__smo<T>(recipient: b256, data: T, coins: u64)
Description: Sends a message data of arbitrary type T and coins amount of the base asset to address recipient.
Constraints: None.
__not(op: T) -> T
Description: Bitwise NOT of op
Constraints: T is an integer type, i.e. u8, u16, u32, u64, u256, b256.
__jmp_mem()
Description: Jumps to MEM[$hp].
Constraints: None.
__slice(item: &[T; N], start: u64, end: u64) -> &[T]
__slice(item: &[T], start: u64, end: u64) -> &[T]
__slice(item: &mut [T; N], start: u64, end: u64) -> &mut [T]
__slice(item: &mut [T], start: u64, end: u64) -> &mut [T]
Description: Slices an array or another slice.
This intrinsic returns a reference to a slice containing the range of elements inside item.
The mutability of reference is defined by the first parameter mutability.
Runtime bound checks are not generated, and must be done manually when and where appropriated. Compile time bound checks are done when possible.
Constraints:
itemis an array or a slice;- when
startis a literal, it must be smaller thanitemlength; - when
endis a literal, it must be smaller than or equal toitemlength; endmust be greater than or equal tostart
__elem_at(item: &[T; N], index: u64) -> &T
__elem_at(item: &[T], index: u64) -> &T
__elem_at(item: &mut [T; N], index: u64) -> &mut T
__elem_at(item: &mut [T], index: u64) -> &mut T
Description: Returns a reference to the indexed element. The mutability of reference is defined by the first parameter mutability.
Runtime bound checks are not generated, and must be done manually when and where appropriated. Compile time bound checks are done when possible.
Constraints:
itemis a reference to an array or a reference to a slice;- when
indexis a literal, it must be smaller thanitemlength;
Attributes
The Sway compiler supports a list of attributes that perform various operations that are useful for building, testing and documenting Sway programs. Below is a list of all available attributes:
Allow
The #[allow(...)] attribute overrides checks so that violations will go unreported. The following checks can be disabled:
#[allow(dead_code)]disable checks for dead code;#[allow(deprecated)]disables checks for usage of deprecated structs, functions and other items.
Doc
The #[doc(..)] attribute specifies documentation.
Line doc comments beginning with exactly three slashes ///, are interpreted as a special syntax for doc attributes. That is, they are equivalent to writing #[doc("...")] around the body of the comment, i.e., /// Foo turns into #[doc("Foo")]
Line comments beginning with //! are doc comments that apply to the module of the source file they are in. That is, they are equivalent to writing #![doc("...")] around the body of the comment. //! module level doc comments should be at the top of Sway files.
Documentation can be generated from doc attributes using forc doc.
Inline
The inline attribute suggests that a copy of the attributed function should be placed in the caller, rather than generating code to call the function where it is defined.
Note: The Sway compiler automatically inlines functions based on internal heuristics. Incorrectly inlining functions can make the program slower, so this attribute should be used with care.
The #[inline(never)] attribute suggests that an inline expansion should never be performed.
The #[inline(always)] attribute suggests that an inline expansion should always be performed.
Note:
#[inline(..)]in every form is a hint, with no requirements on the language to place a copy of the attributed function in the caller.
Payable
The lack of #[payable] implies the method is non-payable. When calling an ABI method that is non-payable, the compiler emits an error if the amount of coins forwarded with the call is not guaranteed to be zero. Note that this is strictly a compile-time check and does not incur any runtime cost.
Storage
In Sway, functions are pure by default but can be opted into impurity via the storage function attribute. The storage attribute may take read and/or write arguments indicating which type of access the function requires.
The #[storage(read)] attribute indicates that a function requires read access to the storage.
The #[storage(write)] attribute indicates that a function requires write access to the storage.
More details in Purity.
Test
The #[test] attribute marks a function to be executed as a test.
The #[test(should_revert)] attribute marks a function to be executed as a test that should revert.
More details in Unit Testing.
Deprecated
The #[deprecated] attribute marks an item as deprecated and makes the compiler emit a warning for every usage of the deprecated item. This warning can be disabled using #[allow(deprecated)].
It is possible to improve the warning message with #[deprecated(note = "your message")]
Fallback
The #[fallback] attribute makes the compiler use the marked function as the contract call fallback function, which means that, when a contract is called, and the contract selection fails, the fallback function will be called instead.
Style Guide
Capitalization
In Sway, structs, traits, and enums are CapitalCase. Modules, variables, and functions are snake_case, constants are SCREAMING_SNAKE_CASE. The compiler will warn you if your capitalization is ever unidiomatic.
Known Issues and Workarounds
Known Issues
- #870: All
implblocks need to be defined before any of the functions they define can be called. This includes sibling functions in the sameimpldeclaration, i.e., functions in animplcan't call each other yet.
Missing Features
- #1182 Arrays in a
storageblock are not yet supported. See the Manual Storage Management section for details on how to usestoreandgetfrom the standard library to manage storage slots directly. Note, however, thatStorageMap<K, V>does support arbitrary types forKandVwithout any limitations.
General
- No compiler optimization passes have been implemented yet, therefore bytecode will be more expensive and larger than it would be in production. Note that eventually the optimizer will support zero-cost abstractions, avoiding the need for developers to go down to inline assembly to produce optimal code.
Behavior Considered Undefined
Sway code that contains any of the following behavior is considered undefined. The compiler is allowed to treat undefined Sway code however it desires, including removing it or replacing it with any other Sway code.
This is not an exhaustive list, it may grow or shrink, there is no formal model of Sway's semantics so there may be more behavior considered undefined. We reserve the right to make some of the listed behavior defined in the future.
- Invalid arithmetic operations (overflows, underflows, division by zero, etc.).
- Misuse of compiler intrinsics.
- Incorrect use of inline assembly.
- Reading and writing
raw_ptrandraw_slice. - Slicing and indexing out of bounds by directly using compiler intrinsics.
- Modifying collections while iterating over them using
Iterators.
Differences From Solidity
This page outlines some of the critical differences between Sway and Solidity, and between the FuelVM and the EVM.
Underlying Virtual Machine
The underlying virtual machine targeted by Sway is the FuelVM, specified here. Solidity targets the Ethereum Virtual Machine (EVM), specified here.
Word Size
Words in the FuelVM are 64 bits (8 bytes), rather than the 256 bits (32 bytes) of the EVM. Therefore, all primitive integers smaller and including u64 are stored in registers; u256, being bigger than the registers, and hashes (the b256 type) are not stored in registers but rather in memory. They are therefore pointers to a 32-byte memory region containing their data.
Unsigned Integers Only
Only unsigned integers are provided as primitives: u8, u16, u32, u64, and u256. Signed integer arithmetic is not available in the FuelVM. Signed integers and signed integer arithmetic can be implemented in high-level libraries if needed.
Global Revert
Panics in the FuelVM (called "reverts" in Solidity and the EVM) are global, i.e. they cannot be caught. A panic will completely and unconditionally revert the stateful effects of a transaction, minus gas used.
Default Safe Math
Math in the FuelVM is by default safe (i.e. any overflow or exception is a panic). Safety checks are performed natively in the VM implementation, rather than at the bytecode level like Solidity's default safe math.
No* Code Size Limit
There is no practical code size limit to Sway contracts. The physical limit is governed by the VM_MAX_RAM VM parameter, which at the time of writing is 64 MiB.
Account Types
Account types in the FuelVM have type-safe wrappers around primitive b256 hashes to clearly distinguish their respective types. The wrapper Address mirrors that of an EOA (Externally Owned Account) and has the ability to hold UTXOs in the context of the EVM. The other wrapper, ContractId, reflects that of a deployed contract in the EVM but cannot hold UTXOs.
Differences From Rust
Sway shares a lot with Rust, especially its syntax. Because they are so similar, you may be surprised or caught off guard when they differ. This page serves to outline, from a high level, some of the syntactic gotchas that you may encounter.
Enum Variant Syntax
In Rust, enums generally take one of three forms: unit variants, which have no inner data, struct variants, which contain named fields, and tuple variants, which contain within them a tuple of data. If you are unfamiliar with these terms, this is what they look like:
// note to those skimming the docs: this is Rust syntax! Not Sway! Don't copy/paste this into a Sway program.
enum Foo {
UnitVariant,
TupleVariant(u32, u64, bool),
StructVariant {
field_one: bool,
field_two: bool
}
}
In Sway, enums are simplified. Enums variants must all specify exactly one type. This type represents their interior data. This is actually isomorphic to what Rust offers, but with a different syntax. You can see the above enum but with Sway syntax below:
// This is equivalent Sway syntax for the above Rust enum.
enum Foo {
UnitVariant: (),
TupleVariant: (u32, u64, bool),
StructVariant: MyStruct,
}
struct MyStruct {
field_one: bool,
field_two: bool,
}
Memory Allocation
In Rust, the borrow checker implements Rust's ownership system
In Sway, there is no borrow checker. This means there is no concept of ownership, borrowing, or lifetimes. Instead, objects are copied and moved similar to C++. Also Sway does not have any destructors nor Drop traits. This means allocated memory lives for the entire transaction and is not deallocated until the end of the transaction. A transaction may allocate up to 64 MB of memory.
Contributing To Sway
Thanks for your interest in contributing to Sway! This document outlines the process for installing and setting up the Sway toolchain for development, as well as some conventions on contributing to Sway.
If you run into any difficulties getting started, you can always ask questions on our Discourse.
Building and setting up a development workspace
See the introduction section for instructions on installing and setting up the Sway toolchain.
Getting the repository
- Visit the Sway repo and fork the project.
- Then clone your forked copy to your local machine and get to work.
git clone https://github.com/FuelLabs/sway
cd sway
Building and testing
The following steps will run the sway test suite and ensure that everything is set up correctly.
First, open a new terminal and start fuel-core with:
fuel-core
Then open a second terminal, cd into the sway repo and run:
cargo run --bin test
After the test suite runs, you should see:
Tests passed.
_n_ tests run (0 skipped)
Congratulations! You've now got everything setup and are ready to start making contributions.
Finding something to work on
There are many ways in which you may contribute to the Sway project, some of which involve coding knowledge and some which do not. A few examples include:
- Reporting bugs
- Adding documentation to the Sway book
- Adding new features or bug fixes for which there is already an open issue
- Making feature requests
Check out our Help Wanted, Sway Book or Good First Issue issues to find a suitable task.
If you are planning something big, for example, related to multiple components or changes current behaviors, make sure to open an issue to discuss with us before starting on the implementation.
Contribution flow
This is a rough outline of what a contributor's workflow looks like:
- Make sure what you want to contribute is already tracked as an issue.
- We may discuss the problem and solution in the issue.
- Create a Git branch from where you want to base your work. This is usually master.
- Write code, add test cases, and commit your work.
- Run tests and make sure all tests pass.
- If the PR contains any breaking changes, add the breaking label to your PR.
- Push your changes to a branch in your fork of the repository and submit a pull request.
- Make sure to mention the issue, which is created at step 1, in the commit message.
- Your PR will be reviewed and some changes may be requested.
- Once you've made changes, your PR must be re-reviewed and approved.
- If the PR becomes out of date, you can use GitHub's 'update branch' button.
- If there are conflicts, you can merge and resolve them locally. Then push to your PR branch. Any changes to the branch will require a re-review.
- Our CI system (Github Actions) automatically tests all authorized pull requests.
- Use Github to merge the PR once approved.
Thanks for your contributions!
Linking issues
Pull requests should be linked to at least one issue in the same repo.
If the pull request resolves the relevant issues, and you want GitHub to close these issues automatically after it merged into the default branch, you can use the syntax (KEYWORD #ISSUE-NUMBER) like this:
close #123
If the pull request links an issue but does not close it, you can use the keyword ref like this:
ref #456
Multiple issues should use full syntax for each issue and separate by a comma, like:
close #123, ref #456
Keywords
The following list contains keywords that are reserved for current or future use by the Sway language. As such, they cannot be used as identifiers. Identifiers are names of functions, variables, parameters, modules, constants, attributes, types or traits, etc.
Keywords Currently in Use
The following is a list of keywords currently in use, with their functionality described.
as- rename items inusestatements, e.g.,use type::a as alias_nameabi- defines a smart contract ABI in a syntactically similar way to traitsbreak- exit a loop immediatelyconst- define constant itemscontinue- continue to the next loop iterationelse- used in conjunction withifconditions for control flow constructsenum- define an enumerationfalse- Boolean false literalfn- define a function or the function pointer typeif- branch based on the result of a conditional expressionimpl- implement inherent or trait functionalitylet- bind a variablematch- exhaustively match a value to patternsmod- define a modulemut- denote mutability in references, or pattern bindingspub- denote public visibility of Sway data structures, traits, or modulesref- bind by referencereturn- return early from a functionSelf- a type alias for the type we are defining or implementingself- method subjectstruct- define a structuretrait- define a traittrue- Boolean true literaltype- define a type alias or associated typeuse- bring symbols into scopewhere- specifies traits for generic typeswhile- loop conditionally based on the result of an expression
Keywords Reserved for Possible Future Use
abstractasyncawaitbecomeboxdodynexternforinloopmacromoveoverrideprivstaticsupertrytypeofunsafeunsizedvirtualyield
Special Keywords
Program Keywords
Keywords associated with defining the type of Sway program to compile
contract- analogous to a deployed API with some database statelibrary- Sway code that defines new common behaviorpredicate- programs that return a Boolean value and which represent ownership of some resource upon execution to truescript- a runnable bytecode on the chain, which executes once to perform a task
Attribute Keywords
Keywords associated with defining the functionality of attributes
allow- overrides checks that would otherwise result in errors or warningsdoc- specifies documentationinline- suggests that a copy of the attributed function should be placed in the caller, rather than generating code to call the function where it is definedpayable- implies method is payable for compile timestorage- declaration that contains a list of stored variablestest- marks a function to be executed as a testdeprecated- marks an item as deprecated
Forc Reference
Forc stands for Fuel Orchestrator. Forc provides a variety of tools and commands for developers working with the Fuel ecosystem, such as scaffolding a new project, formatting, running scripts, deploying contracts, testing contracts, and more. If you're coming from a Rust background, forc is similar to cargo.
If you are new to Forc, see the Forc Project introduction section.
For a comprehensive overview of the Forc CLI commands, see the Commands section.
Manifest Reference
The Forc.toml (the manifest file) is a compulsory file for each package and it is written in [TOML] format. Forc.toml consists of the following fields:
-
[project]— Defines a sway project.name— The name of the project.version— The version of the project.description— A description of the project.authors— The authors of the project.organization— The organization of the project.license— The project license.homepage— URL of the project homepage.repository— URL of the project source repository.documentation— URL of the project documentation.categories— Categories of the project.keywords— Keywords the project.entry— The entry point for the compiler to start parsing from.- For the recommended way of selecting an entry point of large libraries please take a look at: Libraries
implicit-std- Controls whether providedstdversion (with the currentforcversion) will get added as a dependency implicitly. Unless you know what you are doing, leave this as default.forc-version- The minimum forc version required for this project to work properly.metadata- Metadata for the project; can be used by tools which would like to store package configuration inForc.toml.
-
[dependencies]— Defines the dependencies. -
[network]— Defines a network for forc to interact with.url— URL of the network.
-
[build-profile]- Defines the build profiles. -
[patch]- Defines the patches. -
[contract-dependencies]- Defines the contract dependencies.
The [project] section
An example Forc.toml is shown below. Under [project] the following fields are optional:
authorsorganizationversiondescriptionhomepagerepositorydocumentationcategorieskeywords
Also for the following fields, a default value is provided so omitting them is allowed:
entry- (default :main.sw)implicit-std- (default :true)
[project]
authors = ["user"]
entry = "main.sw"
description = "Wallet contract"
version = "1.0.0"
homepage = "https://example.com/"
repository = "https://example.com/"
documentation = "https://example.com/"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
categories = ["example"]
keywords = ["example"]
[project.metadata]
indexing = { namespace = "counter-contract", schema_path = "out/release/counter-contract-abi.json" }
Metadata Section in Forc.toml
The [project.metadata] section provides a dedicated space for external tools and plugins to store their configuration in Forc.toml. The metadata key names are arbitrary and do not need to match the tool's name.
Workspace vs Project Metadata
Metadata can be defined at two levels:
Workspace level - defined in the workspace's root Forc.toml:
[workspace.metadata]
my_tool = { shared_setting = "value" }
Project level - defined in individual project's Forc.toml:
[project.metadata.any_name_here]
option1 = "value"
option2 = "value"
[project.metadata.my_custom_config]
setting1 = "value"
setting2 = "value"
Example for an indexing tool:
[project.metadata.indexing]
namespace = "counter-contract"
schema_path = "out/release/counter-contract-abi.json"
When both workspace and project metadata exist:
- Project-level metadata should take precedence over workspace metadata
- Tools can choose to merge workspace and project settings
- Consider documenting your tool's metadata inheritance behavior
Guidelines for Plugin Developers
Best Practices
- Choose clear, descriptive metadata key names
- Document the exact metadata key name your tool expects
- Don't require
Forc.tomlif tool can function without it - Consider using TOML format for dedicated config files
- Specify how your tool handles workspace vs project metadata
Implementation Notes
- The metadata section is optional
- Forc does not parse metadata contents
- Plugin developers handle their own configuration parsing
- Choose unique metadata keys to avoid conflicts with other tools
Example Use Cases
- Documentation generation settings
- Formatter configurations
- Debugger options
- Wallet integration
- Contract indexing
- Testing frameworks
This allows for a streamlined developer experience while maintaining clear separation between core Forc functionality and third-party tools.
External Tooling Examples
- forc-index-ts: A TypeScript CLI tool for parsing
Forc.tomlmetadata to read contract ABI JSON file. - forc-index-rs: A Rust CLI tool for parsing
Forc.tomlmetadata to read contract ABI JSON file.
The [dependencies] section
The following fields can be provided with a dependency:
version- Desired version of the dependencypath- The path of the dependency (if it is local)git- The URL of the git repo hosting the dependencybranch- The desired branch to fetch from the git repotag- The desired tag to fetch from the git reporev- The desired rev (i.e. commit hash) reference
Please see dependencies for details
The [network] section
For the following fields, a default value is provided so omitting them is allowed:
URL- (default: http://127.0.0.1:4000)
The [build-profile.*] section
The [build-profile] tables provide a way to customize compiler settings such as debug options.
The following fields can be provided for a build-profile:
print-ast- Whether to print out the generated AST or not, defaults to false.print-dca-graph- Whether to print out the computed Dead Code Analysis (DCA) graph (in GraphViz DOT format), defaults to false.print-dca-graph-url-format- The URL format to be used in the generated DOT file, an example for VS Code would be:vscode://file/{path}:{line}:{col}.print-ir- Whether to print out the generated Sway IR (Intermediate Representation) or not, defaults to false.print-asm- Whether to print out the generated ASM (assembler), defaults to false.terse- Terse mode. Limited warning and error output, defaults to false.time_phases- Whether to output the time elapsed over each part of the compilation process, defaults to false.include_tests- Whether or not to include test functions in parsing, type-checking, and code generation. This is set to true by invocations likeforc test, but defaults to false.error_on_warnings- Whether to treat errors as warnings, defaults to false.
There are two default [build-profile] available with every manifest file. These are debug and release profiles. If you want to override these profiles, you can provide them explicitly in the manifest file like the following example:
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
[build-profile.debug]
print-asm = { virtual = false, allocated = false, final = true }
print-ir = { initial = false, final = true, modified = false, passes = []}
terse = false
[build-profile.release]
print-asm = { virtual = true, allocated = false, final = true }
print-ir = { initial = true, final = false, modified = true, passes = ["dce", "sroa"]}
terse = true
Since release and debug are implicitly included in every manifest file, you can use them by just passing --release or by not passing anything (debug is default). For using a user defined build profile there is --build-profile <profile name> option available to the relevant commands. (For an example see forc-build)
Note that providing the corresponding CLI options (like --asm) will override the selected build profile. For example if you pass both --release and --asm all, release build profile is overridden and resulting build profile would have a structure like the following:
print-ast = false
print-ir = { initial = false, final = false, modified = false, passes = []}
print-asm = { virtual = true, allocated = true, final = true }
terse = false
time-phases = false
include-tests = false
error-on-warnings = false
experimental-private-modules = false
The [patch] section
The [patch] section of Forc.toml can be used to override dependencies with other copies. The example provided below patches https://github.com/fuellabs/sway with the test branch of the same repo.
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
[dependencies]
[patch.'https://github.com/fuellabs/sway']
std = { git = "https://github.com/fuellabs/sway", branch = "test" }
In the example above, std is patched with the test branch from std repo. You can also patch git dependencies with dependencies defined with a path.
[patch.'https://github.com/fuellabs/sway']
std = { path = "/path/to/local_std_version" }
Just like std you can also patch dependencies you declared with a git repo.
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
[dependencies]
foo = { git = "https://github.com/foo/foo", branch = "master" }
[patch.'https://github.com/foo']
foo = { git = "https://github.com/foo/foo", branch = "test" }
Note that each key after the [patch] is a URL of the source that is being patched.
The [contract-dependencies] section
The [contract-dependencies] table can be used to declare contract dependencies for a Sway contract or script. Contract dependencies are the set of contracts that our contract or script may interact with. Declaring [contract-dependencies] makes it easier to refer to contracts in your Sway source code without having to manually update IDs each time a new version is deployed. Instead, we can use forc to pin and update contract dependencies just like we do for regular library dependencies.
Contracts declared under [contract-dependencies] are built and pinned just like regular [dependencies] however rather than importing each contract dependency's entire public namespace we instead import their respective contract IDs as CONTRACT_ID constants available via each contract dependency's namespace root. This means you can use a contract dependency's ID as if it were declared as a pub const in the root of the contract dependency package as demonstrated in the example below.
Entries under [contract-dependencies] can be declared in the same way that [dependencies] can be declared. That is, they can refer to the path or git source of another contract. Note that entries under [contract-dependencies] must refer to contracts and will otherwise produce an error.
Example Forc.toml:
[project]
authors = ["user"]
entry = "main.sw"
organization = "Fuel_Labs"
license = "Apache-2.0"
name = "wallet_contract"
[contract-dependencies]
foo = { path = "../foo" }
Example usage:
script;
fn main() {
let foo_id = foo::CONTRACT_ID;
}
Because the ID of a contract is computed deterministically, rebuilding the same contract would always result in the same contract ID. Since two contracts with the same contract ID cannot be deployed on the blockchain, a "salt" factor is needed to modify the contract ID. For each contract dependency declared under [contract-dependencies], salt can be specified. An example is shown below:
[contract-dependencies]
foo = { path = "../foo", salt = "0x1000000000000000000000000000000000000000000000000000000000000000" }
For contract dependencies that do not specify any value for salt, a default of all zeros for salt is implicitly applied.
Workspaces
A workspace is a collection of one or more packages, namely workspace members, that are managed together.
The key points for workspaces are:
- Common
forccommands available for a single package can also be used for a workspace, likeforc buildorforc deploy. - All packages share a common
Forc.lockfile which resides in the root directory of the workspace.
Workspace manifests are declared within Forc.toml files and support the following fields:
An empty workspace can be created with forc new --workspace or forc init --workspace.
The members field
The members field defines which packages are members of the workspace:
[workspace]
members = ["member1", "path/to/member2"]
The members field accepts entries to be given in relative path with respect to the workspace root.
Packages that are located within a workspace directory but are not contained within the members set are ignored.
The [patch] section
The [patch] section can be used to override any dependency in the workspace dependency graph. The usage is the same with package level [patch] section and details can be seen here.
It is not allowed to declare patch table in member of a workspace if the workspace manifest file contains a patch table.
Example:
[workspace]
members = ["member1", "path/to/member2"]
[patch.'https://github.com/fuellabs/sway']
std = { git = "https://github.com/fuellabs/sway", branch = "test" }
In the above example each occurrence of std as a dependency in the workspace will be changed with std from test branch of sway repo.
Some forc commands that support workspaces
forc build- Builds an entire workspace.forc deploy- Builds and deploys all deployable members (i.e, contracts) of the workspace in the correct order.forc run- Builds and runs all scripts of the workspace.forc check- Checks all members of the workspace.forc update- Checks and updates workspace levelForc.lockfile that is shared between workspace members.forc clean- Cleans all output artifacts for each member of the workspace.forc fmt- Formats all members of a workspace.
Dependencies
Forc has a dependency management system which can pull packages using git and ipfs. This allows users to build and share Forc libraries.
Adding a dependency
If your Forc.toml doesn't already have a [dependencies] table, add one. Below, list the package name alongside its source. Currently, forc supports git, ipfs and path sources.
If a git source is specified, forc will fetch the git repository at the given URL and then search for a Forc.toml for a package with the given name anywhere inside the git repository.
The following example adds a library dependency named custom_lib. For git dependencies you may optionally specify a branch, tag, or rev (i.e. commit hash) reference.
[dependencies]
custom_lib = { git = "https://github.com/FuelLabs/custom_lib", branch = "master" }
# custom_lib = { git = "https://github.com/FuelLabs/custom_lib", tag = "v0.0.1" }
# custom_lib = { git = "https://github.com/FuelLabs/custom_lib", rev = "87f80bdf323e2d64e213895d0a639ad468f4deff" }
Depending on a local library using path:
[dependencies]
custom_lib = { path = "../custom_lib" }
For ipfs sources, forc will fetch the specified cid using either a local ipfs node or a public gateway. forc automatically tries to connect to local ipfs node. If it fails, it defaults to using https://ipfs.io/ as a gateway.
The following example adds a dependency with an ipfs source.
[dependencies]
custom_lib = { ipfs = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG" }
Once the package is added, running forc build will automatically download added dependencies.
Updating dependencies
To update dependencies in your Forc directory you can run forc update. For path and ipfs dependencies this will have no effect. For git dependencies with a branch reference, this will update the project to use the latest commit for the given branch.
Here are a list of commands available to forc:
forc addr2line
forc build
forc check
forc clean
forc completions
forc contract-id
forc init
forc new
forc parse-bytecode
forc plugins
forc predicate-root
forc test
forc update
forc template
Plugins
Plugins can be used to extend forc with new commands that go beyond the native commands mentioned in the previous chapter. While the Fuel ecosystem provides a few commonly useful plugins (forc-fmt, forc-client, forc-lsp, forc-explore), anyone can write their own!
Let's install a plugin, forc-explore, and see what's underneath the plugin:
cargo install forc-explore
Check that we have installed forc-explore:
$ forc plugins
Installed Plugins:
forc-explore
forc-explore runs the Fuel Network Explorer, which you can run and check out for yourself:
$ forc explore
Fuel Network Explorer 0.1.1
Running server on http://127.0.0.1:3030
Server::run{addr=127.0.0.1:3030}: listening on http://127.0.0.1:3030
You can visit http://127.0.0.1:3030 to check out the network explorer!
Note that some plugin crates can also provide more than one command. For example, installing the forc-client plugin provides the forc deploy and forc run commands. This is achieved by specifying multiple [[bin]] targets within the forc-client manifest.
Writing your own plugin
We encourage anyone to write and publish their own forc plugin to enhance their development experience.
Your plugin must be named in the format forc-<MY_PLUGIN> and you may use the above template as a starting point. You can use clap and add more subcommands, options and configurations to suit your plugin's needs.
forc-client
The forc plugin for interacting with a Fuel node.
Since transactions are going to require some gas, you need to sign them with an account that has enough coins to pay for them.
We offer multiple ways to sign the transaction:
- Sign the transaction via your local wallet using
forc-clientwhich integrates with our CLI wallet,forc-wallet. - Use the default signer to deploy to a local node
- Use
forc-walletto manually sign transactions, and copy the signed transaction back toforc-client.
The easiest and recommended way to interact with deployed networks such as our testnets is option 1, using forc-client to sign your transactions which reads your default forc-wallet vault. For interacting with local node, we recommend using the second option, which leads forc-client to sign transactions with the private key that comes pre-funded in local environments.
Option 1: Sign transactions via forc-client using your local forc-wallet vault
If you've used forc-wallet before, you'll already have a secure, password-protected vault holding your private key written to your file-system. forc-client is compatible with forc-wallet such that it can read that vault by asking you your password and use your account to sign transactions.
Example:
> forc deploy
Building /Users/yourname/test-projects/test-contract
Finished release [optimized + fuel] target(s) in 11.39s
Confirming transactions [deploy impl-contract]
Network: https://testnet.fuel.network
Wallet: /Users/yourname/.fuel/wallets/.wallet
✔ Wallet password · ********
? Wallet account ›
❯ [0] fuel12pls73y9hnqdqthvduy2x44x48zt8s50pkerf32kq26f2afeqdwq6rj9ar - 0.002197245 ETH
[1] fuel1vzrm6kw9s3tv85gl25lpptsxrdguyzfhq6c8rk07tr6ft5g45nwqqh0uty - 0.001963631 ETH
? Do you agree to sign 1 transaction? (y/n) › yes
Finished deploying impl-contract https://app.fuel.network/contract/0x94b712901f04332682d14c998a5fc5a078ed15321438f46d58d0383200cde43d
Deployed in block https://app.fuel.network/block/5958351
As it can be seen from the example, forc-client asks for your password to decrypt the forc-wallet vault, and list your accounts so that you can select the one you want to fund the transaction with.
Option 2: Using default signer
If you are not interacting with a deployed network, such as testnets, your local fuel-core environment can be structured such that it funds an account by default. Using --default-signer flag with forc-client binaries (run, deploy) will instruct forc-client to sign transactions with this pre-funded account. This makes it a useful command while working against a local node.
Example:
> forc deploy --default-signer
Building /Users/test/test-projects/test-contract
Finished release [optimized + fuel] target(s) in 11.40s
Confirming transactions [deploy impl-contract]
Network: http://127.0.0.1:4000
Finished deploying impl-contract 0xf9fb08ef18ce226954270d6d4f67677d484b8782a5892b3d436572b405407544
Deployed in block 00000001
Option 3: Manually signing through forc-wallet (Deprecated)
This option is for creating the transaction first, signing it manually, and supplying the signed transaction back to forc-client. Since it requires multiple steps, it is more error-prone and not recommended for general use cases. Also this will be deprecated soon.
- Construct the transaction by using either
forc deployorforc run. To do so simply runforc deploy --manual-signorforc run --manual-signwith your desired parameters. For a list of parameters please refer to the forc-deploy or forc-run section of the book. Once you run either command you will be asked the address of the wallet you are going to be signing with. After the address is given the transaction will be generated and you will be given a transaction ID. At this point CLI will actively wait for you to insert the signature. - Take the transaction ID generated in the first step and sign it with
forc wallet sign --account <account_index> tx-id <transaction_id>. This will generate a signature. - Take the signature generated in the second step and provide it to
forc-deploy(orforc-run). Once the signature is provided, the signed transaction will be submitted.
Other useful commands of forc-wallet
- You can see a list of existing accounts with
accountscommand.
forc wallet accounts
- If you want to retrieve the address for an account by its index you can use
accountcommand.
forc wallet account <account_index>
If you want to sign the transaction generated by
forc-deployorforc-runwith an account funded by default once you start your local node, you can pass--default-signerto them. Please note that this will only work against your local node.forc-deploy --default-signerforc-run --default-signer
By default --default-signer flag would sign your transactions with the following private-key:
0xde97d8624a438121b86a1956544bd72ed68cd69f2c99555b08b1e8c51ffd511c
Selecting a target network
By default, local is used for the target network. To interact with the latest testnet, use the --testnet flag. When this flag is passed, transactions created by forc-deploy will be sent to the latest testnet:
forc-deploy --testnet
The same can be done to target mainnet:
forc-deploy --mainnet
It is also possible to pass the exact node URL while using forc-deploy or forc-run which can be done using --node-url flag:
forc-deploy --node-url https://mainnet.fuel.network
Another alternative is the --target option, which provides useful aliases to all targets. For example if you want to deploy to testnet you can use:
forc-deploy --target testnet
Since deploying and running projects on the testnet cost gas, you will need coins to pay for them. You can get some using the testnet faucet.
Delayed transactions
For delayed transactions, you can use the --submit-only flag. This flag allows you to submit the transaction without waiting for its finalization.
One use case for this is multisig transactions, where a deployment transaction may stay in a pending state while waiting for all signatures.
forc-deploy --submit-only
Deployment Artifacts
forc-deploy saves the details of each deployment in the out/deployments folder within the project's root directory. Below is an example of a deployment artifact:
{
"transaction_id": "0xec27bb7a4c8a3b8af98070666cf4e6ea22ca4b9950a0862334a1830520012f5d",
"salt": "0x9e35d1d5ef5724f29e649a3465033f5397d3ebb973c40a1d76bb35c253f0dec7",
"network_endpoint": "http://127.0.0.1:4000",
"chain_id": 0,
"contract_id": "0x767eeaa7af2621e637f9785552620e175d4422b17d4cf0d76335c38808608a7b",
"deployment_size": 68,
"deployed_block_id": "0x915c6f372252be6bc54bd70df6362dae9bf750ba652bf5582d9b31c7023ca6cf"
}
Proxy Contracts
forc-deploy supports deploying proxy contracts automatically if it is enabled in the Forc.toml of the contract.
[project]
name = "test_contract"
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
implicit-std = false
[proxy]
enabled = true
If there is no address field present under the proxy table, like the example above, forc will automatically create a proxy contract based on the SRC-14 implementation from sway-standards. After generating and deploying the proxy contract, the target is set to the current contract, and the owner of the proxy is set to the account that is signing the transaction for deployment.
This means that if you simply enable proxy in the Forc.toml, forc will automatically deploy a proxy contract for you and you do not need to do anything manually aside from signing the deployment transactions for the proxy contract. After deploying the proxy contract, the address is added into the address field of the proxy table.
If you want to update the target of an SRC-14 compliant proxy contract rather than deploying a new one, simply add its address in the address field, like the following example:
[project]
name = "test_contract"
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
implicit-std = false
[proxy]
enabled = true
address = "0xd8c4b07a0d1be57b228f4c18ba7bca0c8655eb6e9d695f14080f2cf4fc7cd946" # example proxy contract address
If an address is present, forc calls into that contract to update its target instead of deploying a new contract. Since a new proxy deployment adds its own address into the Forc.toml automatically, you can simply enable the proxy once and after the initial deployment, forc will keep updating the target accordingly for each new deployment of the same contract.
Large Contracts
For contracts over the maximum contract size limit (currently 100kB) defined by the network, forc-deploy will split the contract into chunks and deploy the contract with multiple transactions using the Rust SDK's loader contract functionality. Chunks that have already been deployed will be reused on subsequent deployments.
Deploying Scripts and Predicates
forc deploy now supports deploying scripts and predicates in addition to contracts. These are deployed as blobs with generated loaders for efficiency.
Scripts and predicates are deployed automatically when you run forc deploy on a project that contains them. The deployment process differs slightly from contract deployment:
- For scripts and predicates, the bytecode is uploaded as a blob.
- A loader is generated that can load and execute the blob.
- The loader bytecode is saved in the project's output directory.
After deployment, you'll find new files in your project's output directory:
- For scripts:
<script_name>-loader.bin - For predicates:
<predicate_name>-loader.binand<predicate_name>-loader-root
The loader files contain the bytecode necessary to load and execute your script or predicate from the deployed blob.
This new deployment method allows for more efficient storage and execution of scripts and predicates on the Fuel network.
Note: Contracts are still deployed directly, not as blobs given that the contract size is under the maximum contract size limit defined by network (currently 100kB).
forc deploy
forc run
forc submit
Forc Call
forc-call is a command-line tool for interacting with deployed Fuel contracts. It allows you to make contract calls, query contract state, and interact with any deployed contract on the Fuel network - all from your command line!
The forc call command is part of the Forc toolchain and is installed alongside other Forc tools.
Getting Started
Here are a few examples of what you can do with forc call:
Call a simple addition function on a deployed contract (in dry-run mode):
contract;
abi ContractABI {
fn add(a: u64, b: u64) -> u64;
}
impl ContractABI for Contract {
fn add(a: u64, b: u64) -> u64 {
a + b
}
}
forc call 0xe18de7c7c8c61a1c706dccb3533caa00ba5c11b5230da4428582abf1b6831b4d \
--abi ./out/debug/counter-contract-abi.json \
add 1 2
Query the owner of a deployed DEX contract on testnet:
forc call \
--testnet \
--abi https://raw.githubusercontent.com/mira-amm/mira-v1-periphery/refs/heads/main/fixtures/mira-amm/mira_amm_contract-abi.json \
0xd5a716d967a9137222219657d7877bd8c79c64e1edb5de9f2901c98ebe74da80 \
owner
Usage
The basic syntax for forc call is:
forc call [OPTIONS] --abi <ABI-PATH/URL> <CONTRACT_ID> <SELECTOR> [ARGS]...
Where the following arguments are required:
CONTRACT_IDis the ID of the deployed contract you want to interact withABI-PATH/URLis the path or URL to the contract's JSON ABI fileSELECTORis the function name (selector) you want to callARGSare the arguments to pass to the function
Type Encoding
When passing arguments to contract functions, values are encoded according to their Sway types. Here's how to format different types:
| Types | Example input | Notes |
|---|---|---|
| bool | true or false | |
| u8, u16, u32, u64, u128, u256 | 42 | |
| b256 | 0x0000000000000000000000000000000000000000000000000000000000000042 or 0000000000000000000000000000000000000000000000000000000000000042 | 0x prefix is optional |
| bytes, RawSlice | 0x42 or 42 | 0x prefix is optional |
| String, StringSlice, StringArray (Fixed-size) | "abc" | |
| Tuple | (42, true) | The types in tuple can be different |
| Array (Fixed-size), Vector (Dynamic) | [42, 128] | The types in array or vector must be the same; i.e. you cannot have [42, true] |
| Struct | {42, 128} | Since structs are packed encoded, the attribute names are not encoded; i.e. {42, 128}; this could represent the following struct Polygon { x: u64, y: u64 } |
| Enum | (Active: true) or (1: true) | Enums are key-val pairs with keys as being variant name (case-sensitive) or variant index (starting from 0) and values as being the variant value; this could represent the following enum MyEnum { Inactive, Active(bool) } |
ABI Support
The ABI (Application Binary Interface) can be provided in two ways.
Local file
forc call <CONTRACT_ID> --abi ./path/to/abi.json <FUNCTION> [ARGS...]
Remote ABI file/URL
forc call <CONTRACT_ID> --abi https://example.com/abi.json <FUNCTION> [ARGS...]
Network Configuration
forc call --node-url http://127.0.0.1:4000 ...
# or
forc call --target local ...
Advanced Usage
Using Wallets
# utilising the forc-wallet
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --wallet
# with an explicit signing key
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --signing-key <KEY>
Asset Transfers
# Native asset transfer
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --amount 100 --live
# Custom asset transfer
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> \
--amount 100 \
--asset-id 0x1234... \
--live
Gas Configuration
# Set gas price
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --gas-price 1
# Forward gas to contract
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --gas-forwarded 1000
# Set maximum fee
forc call <CONTRACT_ID> --abi <PATH> <FUNCTION> --max-fee 5000
Common Use Cases
Contract State Queries
# Read contract state
forc call <CONTRACT_ID> --abi <PATH> get_balance
# Query with parameters
forc call <CONTRACT_ID> --abi <PATH> get_user_info 0x1234...
Token Operations
# Check token balance
forc call <CONTRACT_ID> --abi <PATH> balance_of 0x1234...
# Transfer tokens
forc call <CONTRACT_ID> --abi <PATH> transfer 0x1234... 100 --live
Contract Administration
# Check contract owner
forc call <CONTRACT_ID> --abi <PATH> owner
# Update contract parameters
forc call <CONTRACT_ID> --abi <PATH> update_params 42 --live
Tips and Tricks
- Use
--mode simulateto estimate gas costs before making live transactions - External contracts are automatically detected (via internal simulations), but can be manually specified with
--external-contracts - For complex parameter types (tuples, structs, enums), refer to the parameter types table above
- Always verify contract addresses and ABIs before making live calls
- Use environment variables for sensitive data like signing keys:
SIGNING_KEY=<key>
Troubleshooting
Common issues and solutions
-
ABI Mismatch:
- Ensure the ABI matches the deployed contract
- Verify function selectors match exactly
-
Parameter Type Errors:
- Check parameter formats in the types table
- Ensure correct number of parameters
-
Network Issues:
- Verify node connection
- Check network selection (testnet/mainnet)
-
Transaction Failures:
- Use simulation mode to debug
- Check gas settings
- Verify wallet has sufficient balance
Future Features
The following features are planned for future releases:
- Support direct transfer of asset(s) to addresses
- Function signature based calls without ABI
- Raw calldata input support
- Function selector completion
- Enhanced error messages, debugging, and logging (additional verbosity modes)
forc crypto
forc debug
forc doc
forc explore
forc fmt
forc lsp
forc migrate
forc node
Getting Started
Adding Sway Libs as a Dependency
To import any library, the following dependency should be added to the project's Forc.toml file under [dependencies].
sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.2" }
For reference, here is a complete Forc.toml file:
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "MyProject"
[dependencies]
sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.2" }
NOTE: Be sure to set the tag to the latest release.
Importing Sway Libs to Your Project
Once Sway Libs is a dependency to your project, you may then import a library in your Sway Smart Contract as so:
use sway_libs::<library>::<library_function>;
For example, to import the only_owner() from the Ownership Library, use the following statement at the top of your Sway file:
use sway_libs::ownership::only_owner;
NOTE: All projects currently use
forc 0.66.6,fuels-rs v0.66.6andfuel-core 0.40.0.
Using Sway Libs
Once the library you require has been imported to your project, you may call or use any functions and structures the library provides.
In the following example, we import the Pausable Library and implement the Pausable ABI with it's associated functions.
use sway_libs::pausable::{_is_paused, _pause, _unpause, Pausable};
// Implement the Pausable ABI for our contract
impl Pausable for Contract {
#[storage(write)]
fn pause() {
_pause(); // Call the provided pause function.
}
#[storage(write)]
fn unpause() {
_unpause(); // Call the provided unpause function.
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused() // Call the provided is paused function.
}
}
Any instructions related to using a specific library should be found within the libraries section of the Sway Libs Book.
For implementation details on the libraries please see the Sway Libs Docs.
Running Tests
There are two sets of tests that should be run: inline tests and sdk-harness tests. Please make sure you are using forc v0.63.3 and fuel-core v0.34.0. You can check what version you are using by running the fuelup show command.
Make sure you are in the source directory of this repository sway-libs/<you are here>.
Run the inline tests:
forc test --path libs --release --locked
Once these tests have passed, run the sdk-harness tests:
forc test --path tests --release --locked && cargo test --manifest-path tests/Cargo.toml
NOTE: This may take a while depending on your hardware, future improvements to Sway will decrease build times. After this has been run once, individual test projects may be built on their own to save time.
Asset Library
The Asset Library provides basic helper functions for the SRC-20; Native Asset Standard, SRC-3; Mint and Burn Standard, and the SRC-7; Arbitrary Asset Metadata Standard. It is intended to make development of Native Assets using Sway quick and easy while following the standard's specifications.
For implementation details on the Asset Library please see the Sway Libs Docs.
SRC-20 Functionality
The Base or core of any Asset on the Fuel Network must follow the SRC-20; Native Asset Standard. The Asset Library's Base section supports the SRC-20's implementation.
SRC-3 Functionality
The SRC-3; Mint and Burn Standard prescribes an ABI for how Native Assets on the Fuel Network are minted and burned. The Asset Library's supply section supports the SRC-3's implementation.
SRC-7 Functionality
The SRC-7; Onchain Asset Metadata Standard prescribes an ABI for stateful metadata associated with Native Assets on the Fuel Network. The Asset Library's metadata section supports the SRC-7's implementation.
Base Functionality
For implementation details on the Asset Library base functionality please see the Sway Libs Docs.
Importing the Asset Library Base Functionality
In order to use the Asset Library, Sway Libs and Sway Standards must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started. To add Sway Standards as a dependency please see the Sway Standards Book.
To import the Asset Library Base Functionality and SRC-20 Standard to your Sway Smart Contract, add the following to your Sway file:
contract;
use std::{hash::*, storage::storage_string::*, string::String};
// ANCHOR: import
use sway_libs::asset::base::*;
use standards::src20::*;
// ANCHOR_END: import
// ANCHOR: src20_abi
abi SRC20 {
#[storage(read)]
fn total_assets() -> u64;
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64>;
#[storage(read)]
fn name(asset: AssetId) -> Option<String>;
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String>;
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8>;
}
// ANCHOR_END: src20_abi
// ANCHOR: set_attributes
abi SetAssetAttributes {
#[storage(write)]
fn set_name(asset: AssetId, name: String);
#[storage(write)]
fn set_symbol(asset: AssetId, symbol: String);
#[storage(write)]
fn set_decimals(asset: AssetId, decimals: u8);
}
// ANCHOR_END: set_attributes
// ANCHOR: src20_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
// ANCHOR_END: src20_storage
Integration with the SRC-20 Standard
The SRC-20 definition states that the following abi implementation is required for any Native Asset on Fuel:
contract;
use std::{hash::*, storage::storage_string::*, string::String};
// ANCHOR: import
use sway_libs::asset::base::*;
use standards::src20::*;
// ANCHOR_END: import
// ANCHOR: src20_abi
abi SRC20 {
#[storage(read)]
fn total_assets() -> u64;
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64>;
#[storage(read)]
fn name(asset: AssetId) -> Option<String>;
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String>;
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8>;
}
// ANCHOR_END: src20_abi
// ANCHOR: set_attributes
abi SetAssetAttributes {
#[storage(write)]
fn set_name(asset: AssetId, name: String);
#[storage(write)]
fn set_symbol(asset: AssetId, symbol: String);
#[storage(write)]
fn set_decimals(asset: AssetId, decimals: u8);
}
// ANCHOR_END: set_attributes
// ANCHOR: src20_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
// ANCHOR_END: src20_storage
The Asset Library has the following complimentary functions for each function in the SRC20 abi:
_total_assets()_total_supply()_name()_symbol()_decimals()
The following ABI and functions are also provided to set your SRC-20 standard storage values:
contract;
use std::{hash::*, storage::storage_string::*, string::String};
// ANCHOR: import
use sway_libs::asset::base::*;
use standards::src20::*;
// ANCHOR_END: import
// ANCHOR: src20_abi
abi SRC20 {
#[storage(read)]
fn total_assets() -> u64;
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64>;
#[storage(read)]
fn name(asset: AssetId) -> Option<String>;
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String>;
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8>;
}
// ANCHOR_END: src20_abi
// ANCHOR: set_attributes
abi SetAssetAttributes {
#[storage(write)]
fn set_name(asset: AssetId, name: String);
#[storage(write)]
fn set_symbol(asset: AssetId, symbol: String);
#[storage(write)]
fn set_decimals(asset: AssetId, decimals: u8);
}
// ANCHOR_END: set_attributes
// ANCHOR: src20_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
// ANCHOR_END: src20_storage
_set_name()_set_symbol()_set_decimals()
NOTE The
_set_name(),_set_symbol(), and_set_decimals()functions will set the attributes of an asset unconditionally. External checks should be applied to restrict the setting of attributes.
Setting Up Storage
Once imported, the Asset Library's base functionality should be available. To use them, be sure to add the storage block below to your contract which enables the SRC-20 standard.
contract;
use std::{hash::*, storage::storage_string::*, string::String};
// ANCHOR: import
use sway_libs::asset::base::*;
use standards::src20::*;
// ANCHOR_END: import
// ANCHOR: src20_abi
abi SRC20 {
#[storage(read)]
fn total_assets() -> u64;
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64>;
#[storage(read)]
fn name(asset: AssetId) -> Option<String>;
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String>;
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8>;
}
// ANCHOR_END: src20_abi
// ANCHOR: set_attributes
abi SetAssetAttributes {
#[storage(write)]
fn set_name(asset: AssetId, name: String);
#[storage(write)]
fn set_symbol(asset: AssetId, symbol: String);
#[storage(write)]
fn set_decimals(asset: AssetId, decimals: u8);
}
// ANCHOR_END: set_attributes
// ANCHOR: src20_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
// ANCHOR_END: src20_storage
Implementing the SRC-20 Standard with the Asset Library
To use the Asset Library's base functionly, simply pass the StorageKey from the prescribed storage block. The example below shows the implementation of the SRC-20 standard in combination with the Asset Library with no user defined restrictions or custom functionality.
contract;
// ANCHOR: basic_src20
use sway_libs::asset::base::{_decimals, _name, _symbol, _total_assets, _total_supply};
use standards::src20::SRC20;
use std::{hash::Hash, storage::storage_string::*, string::String};
// The SRC-20 storage block
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
// Implement the SRC-20 Standard for this contract
impl SRC20 for Contract {
#[storage(read)]
fn total_assets() -> u64 {
// Pass the `total_assets` StorageKey to `_total_assets()` from the Asset Library.
_total_assets(storage.total_assets)
}
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64> {
// Pass the `total_supply` StorageKey to `_total_supply()` from the Asset Library.
_total_supply(storage.total_supply, asset)
}
#[storage(read)]
fn name(asset: AssetId) -> Option<String> {
// Pass the `name` StorageKey to `_name_()` from the Asset Library.
_name(storage.name, asset)
}
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String> {
// Pass the `symbol` StorageKey to `_symbol_()` function from the Asset Library.
_symbol(storage.symbol, asset)
}
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8> {
// Pass the `decimals` StorageKey to `_decimals_()` function from the Asset Library.
_decimals(storage.decimals, asset)
}
}
// ANCHOR_END: basic_src20
Setting an Asset's SRC-20 Attributes
To set some the asset attributes for an Asset, use the SetAssetAttributes ABI provided by the Asset Library. The example below shows the implementation of the SetAssetAttributes ABI with no user defined restrictions or custom functionality. It is recommended that the Ownership Library is used in conjunction with the SetAssetAttributes ABI to ensure only a single user has permissions to set an Asset's attributes.
The _set_name(), _set_symbol(), and _set_decimals() functions follows the SRC-20 standard for logging and will emit their respective log when called.
contract;
// ANCHOR: setting_src20_attributes
use sway_libs::asset::base::*;
use std::{hash::Hash, storage::storage_string::*, string::String};
storage {
name: StorageMap<AssetId, StorageString> = StorageMap {},
symbol: StorageMap<AssetId, StorageString> = StorageMap {},
decimals: StorageMap<AssetId, u8> = StorageMap {},
}
impl SetAssetAttributes for Contract {
#[storage(write)]
fn set_name(asset: AssetId, name: String) {
_set_name(storage.name, asset, name);
}
#[storage(write)]
fn set_symbol(asset: AssetId, symbol: String) {
_set_symbol(storage.symbol, asset, symbol);
}
#[storage(write)]
fn set_decimals(asset: AssetId, decimals: u8) {
_set_decimals(storage.decimals, asset, decimals);
}
}
// ANCHOR_END: setting_src20_attributes
NOTE The
_set_name(),_set_symbol(), and_set_decimals()functions will set the attributes of an asset unconditionally. External checks should be applied to restrict the setting of attributes.
Supply Functionality
For implementation details on the Asset Library supply functionality please see the Sway Libs Docs.
Importing the Asset Library Supply Functionality
In order to use the Asset Library, Sway Libs and Sway Standards must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started. To add Sway Standards as a dependency please see the Sway Standards Book.
To import the Asset Library Supply Functionality and SRC-3 Standard to your Sway Smart Contract, add the following to your Sway file:
contract;
use std::hash::Hash;
// ANCHOR: import
use sway_libs::asset::supply::*;
use standards::src3::*;
// ANCHOR_END: import
// ANCHOR: src3_abi
abi SRC3 {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64);
#[payable]
#[storage(read, write)]
fn burn(vault_sub_id: SubId, amount: u64);
}
// ANCHOR_END: src3_abi
// ANCHOR: src3_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
}
// ANCHOR_END: src3_storage
Integration with the SRC-3 Standard
The SRC-3 definition states that the following abi implementation is required for any Native Asset on Fuel which mints and burns tokens:
contract;
use std::hash::Hash;
// ANCHOR: import
use sway_libs::asset::supply::*;
use standards::src3::*;
// ANCHOR_END: import
// ANCHOR: src3_abi
abi SRC3 {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64);
#[payable]
#[storage(read, write)]
fn burn(vault_sub_id: SubId, amount: u64);
}
// ANCHOR_END: src3_abi
// ANCHOR: src3_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
}
// ANCHOR_END: src3_storage
The Asset Library has the following complimentary functions for each function in the SRC3 abi:
_mint()_burn()
NOTE The
_mint()and_burn()functions will mint and burn assets unconditionally. External checks should be applied to restrict the minting and burning of assets.
Setting Up Storage
Once imported, the Asset Library's supply functionality should be available. To use them, be sure to add the storage block below to your contract which enables the SRC-3 standard.
contract;
use std::hash::Hash;
// ANCHOR: import
use sway_libs::asset::supply::*;
use standards::src3::*;
// ANCHOR_END: import
// ANCHOR: src3_abi
abi SRC3 {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64);
#[payable]
#[storage(read, write)]
fn burn(vault_sub_id: SubId, amount: u64);
}
// ANCHOR_END: src3_abi
// ANCHOR: src3_storage
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
}
// ANCHOR_END: src3_storage
Implementing the SRC-3 Standard with the Asset Library
To use either function, simply pass the StorageKey from the prescribed storage block. The example below shows the implementation of the SRC-3 standard in combination with the Asset Library with no user defined restrictions or custom functionality. It is recommended that the Ownership Library is used in conjunction with the Asset Library's supply functionality to ensure only a single user has permissions to mint an Asset.
The _mint() and _burn() functions follows the SRC-20 standard for logging and will emit the TotalSupplyEvent when called.
contract;
use std::hash::*;
// ANCHOR: basic_src3
use sway_libs::asset::supply::{_burn, _mint};
use standards::src3::SRC3;
storage {
total_assets: u64 = 0,
total_supply: StorageMap<AssetId, u64> = StorageMap {},
}
// Implement the SRC-3 Standard for this contract
impl SRC3 for Contract {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64) {
// Pass the StorageKeys to the `_mint()` function from the Asset Library.
_mint(
storage
.total_assets,
storage
.total_supply,
recipient,
sub_id
.unwrap_or(b256::zero()),
amount,
);
}
// Pass the StorageKeys to the `_burn_()` function from the Asset Library.
#[payable]
#[storage(read, write)]
fn burn(sub_id: SubId, amount: u64) {
_burn(storage.total_supply, sub_id, amount);
}
}
// ANCHOR_END: basic_src3
NOTE The
_mint()and_burn()functions will mint and burn assets unconditionally. External checks should be applied to restrict the minting and burning of assets.
Metadata Functionality
For implementation details on the Asset Library metadata functionality please see the Sway Libs Docs.
Importing the Asset Library Metadata Functionality
In order to use the Asset Library, Sway Libs and Sway Standards must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started. To add Sway Standards as a dependency please see the Sway Standards Book.
To import the Asset Library Base Functionality and SRC-7 Standard to your Sway Smart Contract, add the following to your Sway file:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
Integration with the SRC-7 Standard
The SRC-7 definition states that the following abi implementation is required for any Native Asset on Fuel which uses stateful metadata:
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
The Asset Library has the following complimentary data type for the SRC-7 standard:
StorageMetadata
Setting Up Storage
Once imported, the Asset Library's metadata functionality should be available. To use them, be sure to add the storage block below to your contract which enables the SRC-7 standard.
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
Using the StorageMetadata Type
Setting Metadata
As described in the SRC-7 standard, the metadata type is a simple enum of the following types:
b256Bytesu64String
To set some metadata of any of the above types for an Asset, you can use the SetAssetMetadata ABI provided by the Asset Library with the _set_metadata() function. Be sure to follow the SRC-9 standard for your key. It is recommended that the Ownership Library is used in conjunction with the SetAssetMetadata ABI to ensure only a single user has permissions to set an Asset's metadata.
The _set_metadata() function follows the SRC-7 standard for logging and will emit the SetMetadataEvent when called.
contract;
use std::string::String;
use std::bytes::Bytes;
// ANCHOR: setting_src7_attributes
use sway_libs::asset::metadata::*;
use standards::src7::Metadata;
storage {
metadata: StorageMetadata = StorageMetadata {},
}
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: setting_src7_attributes
// ANCHOR: setting_src7_attributes_custom_abi
abi CustomSetAssetMetadata {
#[storage(read, write)]
fn custom_set_metadata(
asset: AssetId,
key: String,
bits256: b256,
bytes: Bytes,
int: u64,
string: String,
);
}
impl CustomSetAssetMetadata for Contract {
#[storage(read, write)]
fn custom_set_metadata(
asset: AssetId,
key: String,
bits256: b256,
bytes: Bytes,
int: u64,
string: String,
) {
let b256_metadata = Metadata::B256(bits256);
let bytes_metadata = Metadata::Bytes(bytes);
let int_metadata = Metadata::Int(int);
let string_metadata = Metadata::String(string);
// your authentication logic here
// set whichever metadata you want
storage.metadata.insert(asset, key, string_metadata);
}
}
// ANCHOR_END: setting_src7_attributes_custom_abi
NOTE The
_set_metadata()function will set the metadata of an asset unconditionally. External checks should be applied to restrict the setting of metadata.
To set the metadata of an Asset, using only one of the above types, you can define a custom ABI and use it as such:
contract;
use std::string::String;
use std::bytes::Bytes;
// ANCHOR: setting_src7_attributes
use sway_libs::asset::metadata::*;
use standards::src7::Metadata;
storage {
metadata: StorageMetadata = StorageMetadata {},
}
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: setting_src7_attributes
// ANCHOR: setting_src7_attributes_custom_abi
abi CustomSetAssetMetadata {
#[storage(read, write)]
fn custom_set_metadata(
asset: AssetId,
key: String,
bits256: b256,
bytes: Bytes,
int: u64,
string: String,
);
}
impl CustomSetAssetMetadata for Contract {
#[storage(read, write)]
fn custom_set_metadata(
asset: AssetId,
key: String,
bits256: b256,
bytes: Bytes,
int: u64,
string: String,
) {
let b256_metadata = Metadata::B256(bits256);
let bytes_metadata = Metadata::Bytes(bytes);
let int_metadata = Metadata::Int(int);
let string_metadata = Metadata::String(string);
// your authentication logic here
// set whichever metadata you want
storage.metadata.insert(asset, key, string_metadata);
}
}
// ANCHOR_END: setting_src7_attributes_custom_abi
NOTE The
_set_metadata()function will set the metadata of an asset unconditionally. External checks should be applied to restrict the setting of metadata.
Implementing the SRC-7 Standard with StorageMetadata
To use the StorageMetadata type, simply get the stored metadata with the associated key and AssetId using the provided _metadata() convenience function. The example below shows the implementation of the SRC-7 standard in combination with the Asset Library's StorageMetadata type and the _metadata() function with no user defined restrictions or custom functionality.
contract;
use std::string::String;
// ANCHOR: basic_src7
use sway_libs::asset::metadata::*;
use standards::src7::{Metadata, SRC7};
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// Implement the SRC-7 Standard for this contract
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
// Return the stored metadata
storage.metadata.get(asset, key)
}
}
// ANCHOR_END: basic_src7
Getting Metadata
To get the metadata for an asset, apart from the above mentioned _metadata() convenience function, you can also use the get() method on the StorageMetadata type, which returns the Metadata type.
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
This results an Option type as the metadata may not be set for the asset and key combination.
If you know that the metadata is set, but you don't know the type, you can use a match statement to access the metadata.
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
If you know that the metadata is set and you know the type, you can use the as_* methods to access the metadata. We also provide is_* methods to check if the metadata is of a specific type.
contract;
use std::{bytes::Bytes, string::String};
// ANCHOR: import
use sway_libs::asset::metadata::{_metadata, _set_metadata, SetAssetMetadata, StorageMetadata};
use standards::src7::*;
// ANCHOR_END: import
// ANCHOR: src7_abi
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
// ANCHOR_END: src7_abi
// ANCHOR: src7_storage
storage {
metadata: StorageMetadata = StorageMetadata {},
}
// ANCHOR_END: src7_storage
// ANCHOR: src7_metadata_convenience_function
impl SRC7 for Contract {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
_metadata(storage.metadata, asset, key)
}
}
// ANCHOR_END: src7_metadata_convenience_function
// ANCHOR: src7_set_metadata
impl SetAssetMetadata for Contract {
#[storage(read, write)]
fn set_metadata(asset: AssetId, key: String, metadata: Metadata) {
// add your authentication logic here
// eg. only_owner()
_set_metadata(storage.metadata, asset, key, metadata);
}
}
// ANCHOR_END: src7_set_metadata
#[storage(read)]
fn get_metadata(asset: AssetId, key: String) {
// ANCHOR: get_metadata
use sway_libs::asset::metadata::*; // To access trait implementations you must import everything using the glob operator.
let metadata: Option<Metadata> = storage.metadata.get(asset, key);
// ANCHOR_END: get_metadata
// ANCHOR: get_metadata_match
match metadata.unwrap() {
Metadata::B256(b256) => {
// do something with b256
},
Metadata::Bytes(bytes) => {
// do something with bytes
},
Metadata::Int(int) => {
// do something with int
},
Metadata::String(string) => {
// do something with string
},
}
// ANCHOR_END: get_metadata_match
// ANCHOR: get_metadata_as
let metadata: Metadata = storage.metadata.get(asset, key).unwrap();
if metadata.is_b256() {
let b256: b256 = metadata.as_b256().unwrap();
// do something with b256
} else if metadata.is_bytes() {
let bytes: Bytes = metadata.as_bytes().unwrap();
// do something with bytes
} else if metadata.is_u64() {
let int: u64 = metadata.as_u64().unwrap();
// do something with int
} else if metadata.is_string() {
let string: String = metadata.as_string().unwrap();
// do something with string
}
// ANCHOR_END: get_metadata_as
}
Admin Library
The Admin library provides a way to block users without an "administrative status" from calling functions within a contract. The Admin Library differs from the Ownership Library as multiple users may have administrative status. The Admin Library is often used when needing administrative calls on a contract that involve multiple users or a whitelist.
This library extends the Ownership Library. The Ownership library must be imported and used to enable the Admin library. Only the contract's owner may add and remove administrative users.
For implementation details on the Admin Library please see the Sway Libs Docs.
Importing the Admin Library
In order to use the Admin Library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Admin Library, be sure to include both the Admin and Ownership Libraries in your import statements.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
Integrating the Admin Library into the Ownership Library
To use the Admin library, be sure to set a contract owner for your contract. The following demonstrates setting a contract owner using the Ownership Library.
library;
// ANCHOR: ownership_integration
use sway_libs::{admin::add_admin, ownership::initialize_ownership};
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner set in the constructor above.
add_admin(new_admin);
}
// ANCHOR_END: ownership_integration
Basic Functionality
Adding an Admin
To add a new admin to a contract, call the add_admin() function.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
NOTE Only the contract's owner may call this function. Please see the example above to set a contract owner.
Removing an Admin
To remove an admin from a contract, call the revoke_admin() function.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
NOTE Only the contract's owner may call this function. Please see the example above to set a contract owner.
Applying Restrictions
To restrict a function to only an admin, call the only_admin() function.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
NOTE: Admins and the contract's owner are independent of one another.
only_admin()will revert if called by the contract's owner.
To restrict a function to only an admin or the contract's owner, call the only_owner_or_admin() function.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
Checking Admin Status
To check the administrative privileges of a user, call the is_admin() function.
library;
mod owner_integration;
// ANCHOR: import
use sway_libs::{admin::*, ownership::*};
// ANCHOR_END: import
// ANCHOR: add_admin
#[storage(read, write)]
fn add_a_admin(new_admin: Identity) {
// Can only be called by contract's owner.
add_admin(new_admin);
}
// ANCHOR_END: add_admin
// ANCHOR: remove_admin
#[storage(read, write)]
fn remove_an_admin(old_admin: Identity) {
// Can only be called by contract's owner.
revoke_admin(old_admin);
}
// ANCHOR_END: remove_admin
// ANCHOR: only_admin
#[storage(read)]
fn only_owner_may_call() {
only_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: only_admin
// ANCHOR: both_admin
#[storage(read)]
fn both_owner_or_admin_may_call() {
only_owner_or_admin();
// Only an admin may reach this line.
}
// ANCHOR_END: both_admin
// ANCHOR: check_admin
#[storage(read)]
fn check_if_admin(admin: Identity) {
let status = is_admin(admin);
assert(status);
}
// ANCHOR_END: check_admin
Ownership Library
The Ownership Library provides a straightforward way to restrict specific calls in a Sway contract to a single owner. Its design follows the SRC-5 standard from Sway Standards and offers a set of functions to initialize, verify, revoke, and transfer ownership.
For implementation details, visit the Sway Libs Docs.
Importing the Ownership Library
-
Add Sway Libs to
Forc.toml
Please see the Getting Started guide for instructions on adding Sway Libs as a dependency. -
Add Sway Standards to
Forc.toml
Refer to the Sway Standards Book to add Sway Standards. -
Import the Ownership Library
To import the Ownership Library and the SRC-5 standard, include the following in your Sway file:library;
// ANCHOR: import use sway_libs::ownership::; use standards::src5::; // ANCHOR_END: import
// ANCHOR: integrate_with_src5 use sway_libs::ownership::_owner; use standards::src5::{SRC5, State};
impl SRC5 for Contract { #[storage(read)] fn owner() -> State { _owner() } } // ANCHOR_END: integrate_with_src5
// ANCHOR: initialize #[storage(read, write)] fn my_constructor(new_owner: Identity) { initialize_ownership(new_owner); } // ANCHOR_END: initialize
// ANCHOR: only_owner #[storage(read)] fn only_owner_may_call() { only_owner(); // Only the contract's owner may reach this line. } // ANCHOR_END: only_owner
// ANCHOR: state #[storage(read)] fn get_owner_state() { let owner: State = _owner(); } // ANCHOR_END: state
// ANCHOR: transfer_ownership #[storage(read, write)] fn transfer_contract_ownership(new_owner: Identity) { // The caller must be the current owner. transfer_ownership(new_owner); } // ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership #[storage(read, write)] fn renounce_contract_owner() { // The caller must be the current owner. renounce_ownership(); // Now no one owns the contract. } // ANCHOR: renouncing_ownership
## Integrating the Ownership Library into the SRC-5 Standard
When integrating the Ownership Library with [SRC-5](https://docs.fuel.network/docs/sway-standards/src-5-ownership/), ensure that the `SRC5` trait from **Sway Standards** is implemented in your contract, as shown below. The `_owner()` function from this library is used to fulfill the SRC-5 requirement of exposing the ownership state.
```sway
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Basic Usage
Setting a Contract Owner
Establishes the initial ownership state by calling initialize_ownership(new_owner). This can only be done once, typically in your contract's constructor.
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Applying Restrictions
Protect functions so only the owner can call them by invoking only_owner() at the start of those functions.
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Checking the Ownership Status
To retrieve the current ownership state, call _owner().
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Transferring Ownership
To transfer ownership from the current owner to a new owner, call transfer_ownership(new_owner).
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Renouncing Ownership
To revoke ownership entirely and disallow the assignment of a new owner, call renounce_ownership().
library;
// ANCHOR: import
use sway_libs::ownership::*;
use standards::src5::*;
// ANCHOR_END: import
// ANCHOR: integrate_with_src5
use sway_libs::ownership::_owner;
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
// ANCHOR_END: integrate_with_src5
// ANCHOR: initialize
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// ANCHOR_END: initialize
// ANCHOR: only_owner
#[storage(read)]
fn only_owner_may_call() {
only_owner();
// Only the contract's owner may reach this line.
}
// ANCHOR_END: only_owner
// ANCHOR: state
#[storage(read)]
fn get_owner_state() {
let owner: State = _owner();
}
// ANCHOR_END: state
// ANCHOR: transfer_ownership
#[storage(read, write)]
fn transfer_contract_ownership(new_owner: Identity) {
// The caller must be the current owner.
transfer_ownership(new_owner);
}
// ANCHOR: transfer_ownership
// ANCHOR: renouncing_ownership
#[storage(read, write)]
fn renounce_contract_owner() {
// The caller must be the current owner.
renounce_ownership();
// Now no one owns the contract.
}
// ANCHOR: renouncing_ownership
Events
OwnershipRenounced
Emitted when ownership is revoked.
- Fields:
previous_owner: Identity of the owner prior to revocation.
OwnershipSet
Emitted when initial ownership is set.
- Fields:
new_owner: Identity of the newly set owner.
OwnershipTransferred
Emitted when ownership is transferred from one owner to another.
- Fields:
new_owner: Identity of the new owner.previous_owner: Identity of the prior owner.
Errors
InitializationError
- Variants:
CannotReinitialized: Thrown when attempting to initialize ownership if the owner is already set.
AccessError
- Variants:
NotOwner: Thrown when a function restricted to the owner is called by a non-owner.
Example Integration
Below is a example illustrating how to use this library within a Sway contract:
// ANCHOR: example_contract
contract;
use sway_libs::ownership::{
_owner,
initialize_ownership,
only_owner,
renounce_ownership,
transfer_ownership,
};
use standards::src5::{SRC5, State};
impl SRC5 for Contract {
#[storage(read)]
fn owner() -> State {
_owner()
}
}
abi MyContract {
#[storage(read, write)]
fn constructor(new_owner: Identity);
#[storage(read)]
fn restricted_action();
#[storage(read, write)]
fn change_owner(new_owner: Identity);
#[storage(read, write)]
fn revoke_ownership();
#[storage(read)]
fn get_current_owner() -> State;
}
impl MyContract for Contract {
#[storage(read, write)]
fn constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
// A function restricted to the owner
#[storage(read)]
fn restricted_action() {
only_owner();
// Protected action
}
// Transfer ownership
#[storage(read, write)]
fn change_owner(new_owner: Identity) {
transfer_ownership(new_owner);
}
// Renounce ownership
#[storage(read, write)]
fn revoke_ownership() {
renounce_ownership();
}
// Get current owner state
#[storage(read)]
fn get_current_owner() -> State {
_owner()
}
}
// ANCHOR: example_contract
- Initialization: Call
constructor(new_owner)once to set the initial owner. - Restricted Calls: Use
only_owner()to guard any owner-specific functions. - Ownership Checks: Retrieve the current owner state via
_owner(). - Transfer or Renounce: Use
transfer_ownership(new_owner)orrenounce_ownership()for ownership modifications.
Pausable Library
The Pausable library allows contracts to implement an emergency stop mechanism. This can be useful for scenarios such as having an emergency switch to freeze all transactions in the event of a large bug.
It is highly encouraged to use the Ownership Library in combination with the Pausable Library to ensure that only a single administrative user has the ability to pause your contract.
For implementation details on the Pausable Library please see the Sway Libs Docs.
Importing the Pausable Library
In order to use the Pausable library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Pausable Library to your Sway Smart Contract, add the following to your Sway file:
contract;
// ANCHOR: import
use sway_libs::pausable::*;
// ANCHOR_END: import
// ANCHOR: pausable_impl
use sway_libs::pausable::{_is_paused, _pause, _unpause, Pausable};
impl Pausable for Contract {
#[storage(write)]
fn pause() {
_pause();
}
#[storage(write)]
fn unpause() {
_unpause();
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused()
}
}
// ANCHOR_END: pausable_impl
// ANCHOR: require_paused
use sway_libs::pausable::require_paused;
#[storage(read)]
fn require_paused_state() {
require_paused();
// This comment will only ever be reached if the contract is in the paused state
}
// ANCHOR_END: require_paused
// ANCHOR: require_not_paused
use sway_libs::pausable::require_not_paused;
#[storage(read)]
fn require_not_paused_state() {
require_not_paused();
// This comment will only ever be reached if the contract is in the unpaused state
}
// ANCHOR_END: require_not_paused
Basic Functionality
Implementing the Pausable abi
The Pausable Library has two states:
PausedUnpaused
By default, your contract will start in the Unpaused state. To pause your contract, you may call the _pause() function. The example below provides a basic pausable contract using the Pausable Library's Pausable abi without any restrictions such as an administrator.
contract;
// ANCHOR: import
use sway_libs::pausable::*;
// ANCHOR_END: import
// ANCHOR: pausable_impl
use sway_libs::pausable::{_is_paused, _pause, _unpause, Pausable};
impl Pausable for Contract {
#[storage(write)]
fn pause() {
_pause();
}
#[storage(write)]
fn unpause() {
_unpause();
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused()
}
}
// ANCHOR_END: pausable_impl
// ANCHOR: require_paused
use sway_libs::pausable::require_paused;
#[storage(read)]
fn require_paused_state() {
require_paused();
// This comment will only ever be reached if the contract is in the paused state
}
// ANCHOR_END: require_paused
// ANCHOR: require_not_paused
use sway_libs::pausable::require_not_paused;
#[storage(read)]
fn require_not_paused_state() {
require_not_paused();
// This comment will only ever be reached if the contract is in the unpaused state
}
// ANCHOR_END: require_not_paused
Applying Paused Restrictions
When developing a contract, you may want to lock functions down to a specific state. To do this, you may call either of the require_paused() or require_not_paused() functions. The example below shows these functions in use.
contract;
// ANCHOR: import
use sway_libs::pausable::*;
// ANCHOR_END: import
// ANCHOR: pausable_impl
use sway_libs::pausable::{_is_paused, _pause, _unpause, Pausable};
impl Pausable for Contract {
#[storage(write)]
fn pause() {
_pause();
}
#[storage(write)]
fn unpause() {
_unpause();
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused()
}
}
// ANCHOR_END: pausable_impl
// ANCHOR: require_paused
use sway_libs::pausable::require_paused;
#[storage(read)]
fn require_paused_state() {
require_paused();
// This comment will only ever be reached if the contract is in the paused state
}
// ANCHOR_END: require_paused
// ANCHOR: require_not_paused
use sway_libs::pausable::require_not_paused;
#[storage(read)]
fn require_not_paused_state() {
require_not_paused();
// This comment will only ever be reached if the contract is in the unpaused state
}
// ANCHOR_END: require_not_paused
contract;
// ANCHOR: import
use sway_libs::pausable::*;
// ANCHOR_END: import
// ANCHOR: pausable_impl
use sway_libs::pausable::{_is_paused, _pause, _unpause, Pausable};
impl Pausable for Contract {
#[storage(write)]
fn pause() {
_pause();
}
#[storage(write)]
fn unpause() {
_unpause();
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused()
}
}
// ANCHOR_END: pausable_impl
// ANCHOR: require_paused
use sway_libs::pausable::require_paused;
#[storage(read)]
fn require_paused_state() {
require_paused();
// This comment will only ever be reached if the contract is in the paused state
}
// ANCHOR_END: require_paused
// ANCHOR: require_not_paused
use sway_libs::pausable::require_not_paused;
#[storage(read)]
fn require_not_paused_state() {
require_not_paused();
// This comment will only ever be reached if the contract is in the unpaused state
}
// ANCHOR_END: require_not_paused
Using the Ownership Library with the Pausable Library
It is highly recommended to integrate the Ownership Library with the Pausable Library and apply restrictions the pause() and unpause() functions. This will ensure that only a single user may pause and unpause a contract in cause of emergency. Failure to apply this restriction will allow any user to obstruct a contract's functionality.
The follow example implements the Pausable abi and applies restrictions to it's pause/unpause functions. The owner of the contract must be set in a constructor defined by MyConstructor in this example.
contract;
// ANCHOR: impl_with_ownership
use sway_libs::{
ownership::{
initialize_ownership,
only_owner,
},
pausable::{
_is_paused,
_pause,
_unpause,
Pausable,
},
};
abi MyConstructor {
#[storage(read, write)]
fn my_constructor(new_owner: Identity);
}
impl MyConstructor for Contract {
#[storage(read, write)]
fn my_constructor(new_owner: Identity) {
initialize_ownership(new_owner);
}
}
impl Pausable for Contract {
#[storage(write)]
fn pause() {
// Add the `only_owner()` check to ensure only the owner may unpause this contract.
only_owner();
_pause();
}
#[storage(write)]
fn unpause() {
// Add the `only_owner()` check to ensure only the owner may unpause this contract.
only_owner();
_unpause();
}
#[storage(read)]
fn is_paused() -> bool {
_is_paused()
}
}
// ANCHOR_END: impl_with_ownership
Reentrancy Guard Library
The Reentrancy Guard Library provides an API to check for and disallow reentrancy on a contract. A reentrancy attack happens when a function is externally invoked during its execution, allowing it to be run multiple times in a single transaction.
The reentrancy check is used to check if a contract ID has been called more than once in the current call stack.
A reentrancy, or "recursive call" attack can cause some functions to behave in unexpected ways. This can be prevented by asserting a contract has not yet been called in the current transaction. An example can be found here.
For implementation details on the Reentrancy Guard Library please see the Sway Libs Docs.
Importing the Reentrancy Guard Library
In order to use the Reentrancy Guard library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Reentrancy Guard Library to your Sway Smart Contract, add the following to your Sway file:
contract;
// ANCHOR: import
use sway_libs::reentrancy::*;
// ANCHOR_END: import
// ANCHOR: reentrancy_guard
use sway_libs::reentrancy::reentrancy_guard;
abi MyContract {
fn my_non_reentrant_function();
}
impl MyContract for Contract {
fn my_non_reentrant_function() {
reentrancy_guard();
// my code here
}
}
// ANCHOR_END: reentrancy_guard
// ANCHOR: is_reentrant
use sway_libs::reentrancy::is_reentrant;
fn check_if_reentrant() {
assert(!is_reentrant());
}
// ANCHOR_END: is_reentrant
Basic Functionality
Once imported, using the Reentrancy Library can be done by calling one of the two functions:
is_reentrant() -> boolreentrancy_guard()
Using the Reentrancy Guard
Once imported, using the Reentrancy Guard Library can be used by calling the reentrancy_guard() in your Sway Smart Contract. The following shows a Sway Smart Contract that applies the Reentrancy Guard Library:
contract;
// ANCHOR: import
use sway_libs::reentrancy::*;
// ANCHOR_END: import
// ANCHOR: reentrancy_guard
use sway_libs::reentrancy::reentrancy_guard;
abi MyContract {
fn my_non_reentrant_function();
}
impl MyContract for Contract {
fn my_non_reentrant_function() {
reentrancy_guard();
// my code here
}
}
// ANCHOR_END: reentrancy_guard
// ANCHOR: is_reentrant
use sway_libs::reentrancy::is_reentrant;
fn check_if_reentrant() {
assert(!is_reentrant());
}
// ANCHOR_END: is_reentrant
Checking Reentrancy Status
To check if the current caller is a reentrant, you may call the is_reentrant() function.
contract;
// ANCHOR: import
use sway_libs::reentrancy::*;
// ANCHOR_END: import
// ANCHOR: reentrancy_guard
use sway_libs::reentrancy::reentrancy_guard;
abi MyContract {
fn my_non_reentrant_function();
}
impl MyContract for Contract {
fn my_non_reentrant_function() {
reentrancy_guard();
// my code here
}
}
// ANCHOR_END: reentrancy_guard
// ANCHOR: is_reentrant
use sway_libs::reentrancy::is_reentrant;
fn check_if_reentrant() {
assert(!is_reentrant());
}
// ANCHOR_END: is_reentrant
Cross Contract Reentrancy
Cross-Contract Reentrancy is not possible on Fuel due to the use of Native Assets. As such, no contract calls are performed when assets are transferred. However standard security practices when relying on other contracts for state should still be applied, especially when making external calls.
Bytecode Library
The Bytecode Library allows for on-chain verification and computation of bytecode roots for contracts and predicates.
A bytecode root for a contract and predicate is the Merkle root of the binary Merkle tree with each leaf being 16KiB of instructions. This library will compute any contract's or predicate's bytecode root/address allowing for the verification of deployed contracts and generation of predicate addresses on-chain.
For implementation details on the Bytecode Library please see the Sway Libs Docs.
Importing the Bytecode Library
In order to use the Bytecode Library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Bytecode Library to your Sway Smart Contract, add the following to your Sway file:
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Using the Bytecode Library In Sway
Once imported, using the Bytecode Library is as simple as calling the desired function. Here is a list of function definitions that you may use.
compute_bytecode_root()compute_bytecode_root_with_configurables()compute_predicate_address()compute_predicate_address_with_configurables()predicate_address_from_root()swap_configurables()verify_contract_bytecode()verify_contract_bytecode_with_configurables()verify_predicate_address()verify_predicate_address_with_configurables()
Known Issues
Please note that if you are passing the bytecode from the SDK and are including configurable values, the Vec<u8> bytecode provided must be copied to be mutable. The following can be added to make your bytecode mutable:
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Basic Functionality
The examples below are intended for internal contract calls. If you are passing bytecode from the SDK, please follow the steps listed above in known issues to avoid the memory ownership error.
Swapping Configurables
Given some bytecode, you may swap the configurables of both Contracts and Predicates by calling the swap_configurables() function.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Contracts
Computing the Bytecode Root
To compute a contract's bytecode root you may call the compute_bytecode_root() or compute_bytecode_root_with_configurables() functions.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Verifying a Contract's Bytecode Root
To verify a contract's bytecode root you may call verify_bytecode_root() or verify_contract_bytecode_with_configurables() functions.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Predicates
Computing the Address from Bytecode
To compute a predicate's address you may call the compute_predicate_address() or compute_predicate_address_with_configurables() functions.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Computing the Address from a Root
If you have the root of a predicate, you may compute it's corresponding predicate address by calling the predicate_address_from_root() function.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Verifying the Address
To verify a predicates's address you may call verify_predicate_address() or verify_predicate_address_with_configurables() functions.
library;
use std::alloc::alloc_bytes;
// ANCHOR: import
use sway_libs::bytecode::*;
// ANCHOR_END: import
// ANCHOR: known_issue
fn make_mutable(not_mutable_bytecode: Vec<u8>) {
// Copy the bytecode to a newly allocated memory to avoid memory ownership error.
let mut bytecode_slice = raw_slice::from_parts::<u8>(
alloc_bytes(not_mutable_bytecode.len()),
not_mutable_bytecode
.len(),
);
not_mutable_bytecode
.ptr()
.copy_bytes_to(bytecode_slice.ptr(), not_mutable_bytecode.len());
let mut bytecode_vec = Vec::from(bytecode_slice);
// You may now use `bytecode_vec` in your computation and verification function calls
}
// ANCHOR_END: known_issue
// ANCHOR: swap_configurables
fn swap(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let resulting_bytecode: Vec<u8> = swap_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: swap_configurables
// ANCHOR: compute_bytecode_root
fn compute_bytecode(my_bytecode: Vec<u8>) {
let root: BytecodeRoot = compute_bytecode_root(my_bytecode);
}
fn compute_bytecode_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let root: BytecodeRoot = compute_bytecode_root_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_bytecode_root
// ANCHOR: verify_contract_bytecode
fn verify_contract(my_contract: ContractId, my_bytecode: Vec<u8>) {
verify_contract_bytecode(my_contract, my_bytecode);
// By reaching this line the contract has been verified to match the bytecode provided.
}
fn verify_contract_configurables(
my_contract: ContractId,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_contract_bytecode_with_configurables(my_contract, my_bytecode, my_configurables);
// By reaching this line the contract has been verified to match the bytecode provided.
}
// ANCHOR_END: verify_contract_bytecode
// ANCHOR: compute_predicate_address
fn compute_predicate(my_bytecode: Vec<u8>) {
let address: Address = compute_predicate_address(my_bytecode);
}
fn compute_predicate_configurables(
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
let address: Address = compute_predicate_address_with_configurables(my_bytecode, my_configurables);
}
// ANCHOR_END: compute_predicate_address
// ANCHOR: predicate_address_from_root
fn predicate_address(my_root: BytecodeRoot) {
let address: Address = predicate_address_from_root(my_root);
}
// ANCHOR_END: predicate_address_from_root
// ANCHOR: verify_predicate_address
fn verify_predicate(my_predicate: Address, my_bytecode: Vec<u8>) {
verify_predicate_address(my_predicate, my_bytecode);
// By reaching this line the predicate bytecode matches the address provided.
}
fn verify_predicate_configurables(
my_predicate: Address,
my_bytecode: Vec<u8>,
my_configurables: ContractConfigurables,
) {
let mut my_bytecode = my_bytecode;
verify_predicate_address_with_configurables(my_predicate, my_bytecode, my_configurables);
// By reaching this line the predicate bytecode matches the address provided.
}
// ANCHOR_END: verify_predicate_address
Merkle Library
Merkle trees allow for on-chain verification of off-chain data. With the merkle root posted on-chain, the generation of proofs off-chain can provide verifiably true data.
For implementation details on the Merkle Library please see the Sway Libs Docs.
Importing the Merkle Library
In order to use the Merkle Library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Merkle Library to your Sway Smart Contract, add the following to your Sway file:
contract;
// ANCHOR: import
use sway_libs::merkle::binary_proof::*;
// ANCHOR_END: import
abi MerkleExample {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool;
}
impl MerkleExample for Contract {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool {
verify_proof(key, leaf, merkle_root, num_leaves, proof)
}
}
// ANCHOR: leaf_digest
fn compute_leaf(hashed_data: b256) {
let leaf: b256 = leaf_digest(hashed_data);
}
// ANCHOR_END: leaf_digest
// ANCHOR: node_digest
fn compute_node(leaf_a: b256, leaf_b: b256) {
let node: b256 = node_digest(leaf_a, leaf_b);
}
// ANCHOR_END: node_digest
// ANCHOR: process_proof
fn process(key: u64, leaf: b256, num_leaves: u64, proof: Vec<b256>) {
let merkle_root: b256 = process_proof(key, leaf, num_leaves, proof);
}
// ANCHOR_END: process_proof
// ANCHOR: verify_proof
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) {
assert(verify_proof(key, leaf, merkle_root, num_leaves, proof));
}
// ANCHOR_END: verify_proof
Using the Merkle Proof Library In Sway
Once imported, using the Merkle Proof library is as simple as calling the desired function. Here is a list of function definitions that you may use.
leaf_digest()node_digest()process_proof()verify_proof()
Basic Functionality
Computing Leaves and Nodes
The Binary Proof currently allows for you to compute leaves and nodes of a merkle tree given the appropriate hash digest.
To compute a leaf use the leaf_digest() function:
contract;
// ANCHOR: import
use sway_libs::merkle::binary_proof::*;
// ANCHOR_END: import
abi MerkleExample {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool;
}
impl MerkleExample for Contract {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool {
verify_proof(key, leaf, merkle_root, num_leaves, proof)
}
}
// ANCHOR: leaf_digest
fn compute_leaf(hashed_data: b256) {
let leaf: b256 = leaf_digest(hashed_data);
}
// ANCHOR_END: leaf_digest
// ANCHOR: node_digest
fn compute_node(leaf_a: b256, leaf_b: b256) {
let node: b256 = node_digest(leaf_a, leaf_b);
}
// ANCHOR_END: node_digest
// ANCHOR: process_proof
fn process(key: u64, leaf: b256, num_leaves: u64, proof: Vec<b256>) {
let merkle_root: b256 = process_proof(key, leaf, num_leaves, proof);
}
// ANCHOR_END: process_proof
// ANCHOR: verify_proof
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) {
assert(verify_proof(key, leaf, merkle_root, num_leaves, proof));
}
// ANCHOR_END: verify_proof
To compute a node given two leaves, use the node_digest() function:
contract;
// ANCHOR: import
use sway_libs::merkle::binary_proof::*;
// ANCHOR_END: import
abi MerkleExample {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool;
}
impl MerkleExample for Contract {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool {
verify_proof(key, leaf, merkle_root, num_leaves, proof)
}
}
// ANCHOR: leaf_digest
fn compute_leaf(hashed_data: b256) {
let leaf: b256 = leaf_digest(hashed_data);
}
// ANCHOR_END: leaf_digest
// ANCHOR: node_digest
fn compute_node(leaf_a: b256, leaf_b: b256) {
let node: b256 = node_digest(leaf_a, leaf_b);
}
// ANCHOR_END: node_digest
// ANCHOR: process_proof
fn process(key: u64, leaf: b256, num_leaves: u64, proof: Vec<b256>) {
let merkle_root: b256 = process_proof(key, leaf, num_leaves, proof);
}
// ANCHOR_END: process_proof
// ANCHOR: verify_proof
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) {
assert(verify_proof(key, leaf, merkle_root, num_leaves, proof));
}
// ANCHOR_END: verify_proof
NOTE Order matters when computing a node.
Computing the Merkle Root
To compute a Merkle root given a proof, use the process_proof() function.
contract;
// ANCHOR: import
use sway_libs::merkle::binary_proof::*;
// ANCHOR_END: import
abi MerkleExample {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool;
}
impl MerkleExample for Contract {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool {
verify_proof(key, leaf, merkle_root, num_leaves, proof)
}
}
// ANCHOR: leaf_digest
fn compute_leaf(hashed_data: b256) {
let leaf: b256 = leaf_digest(hashed_data);
}
// ANCHOR_END: leaf_digest
// ANCHOR: node_digest
fn compute_node(leaf_a: b256, leaf_b: b256) {
let node: b256 = node_digest(leaf_a, leaf_b);
}
// ANCHOR_END: node_digest
// ANCHOR: process_proof
fn process(key: u64, leaf: b256, num_leaves: u64, proof: Vec<b256>) {
let merkle_root: b256 = process_proof(key, leaf, num_leaves, proof);
}
// ANCHOR_END: process_proof
// ANCHOR: verify_proof
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) {
assert(verify_proof(key, leaf, merkle_root, num_leaves, proof));
}
// ANCHOR_END: verify_proof
Verifying a Proof
To verify a proof against a merkle root, use the verify_proof() function.
contract;
// ANCHOR: import
use sway_libs::merkle::binary_proof::*;
// ANCHOR_END: import
abi MerkleExample {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool;
}
impl MerkleExample for Contract {
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) -> bool {
verify_proof(key, leaf, merkle_root, num_leaves, proof)
}
}
// ANCHOR: leaf_digest
fn compute_leaf(hashed_data: b256) {
let leaf: b256 = leaf_digest(hashed_data);
}
// ANCHOR_END: leaf_digest
// ANCHOR: node_digest
fn compute_node(leaf_a: b256, leaf_b: b256) {
let node: b256 = node_digest(leaf_a, leaf_b);
}
// ANCHOR_END: node_digest
// ANCHOR: process_proof
fn process(key: u64, leaf: b256, num_leaves: u64, proof: Vec<b256>) {
let merkle_root: b256 = process_proof(key, leaf, num_leaves, proof);
}
// ANCHOR_END: process_proof
// ANCHOR: verify_proof
fn verify(
merkle_root: b256,
key: u64,
leaf: b256,
num_leaves: u64,
proof: Vec<b256>,
) {
assert(verify_proof(key, leaf, merkle_root, num_leaves, proof));
}
// ANCHOR_END: verify_proof
Using the Merkle Proof Library with Fuels-rs
To generate a Merkle Tree and corresponding proof for your Sway Smart Contract, use the Fuel-Merkle crate.
Importing Into Your Project
The import the Fuel-Merkle crate, the following should be added to the project's Cargo.toml file under [dependencies]:
fuel-merkle = { version = "0.50.0" }
NOTE Make sure to use the latest version of the fuel-merkle crate.
Importing Into Your Rust File
The following should be added to your Rust file to use the Fuel-Merkle crate.
// ANCHOR: import
use fuel_merkle::binary::in_memory::MerkleTree;
// ANCHOR_END: import
use fuels::{prelude::*, types::Bits256};
use sha2::{Digest, Sha256};
pub const LEAF: u8 = 0x00;
// Load abi from json
abigen!(Contract(
name = "MerkleExample",
abi = "merkle/out/release/merkle_examples-abi.json"
));
async fn get_contract_instance() -> (MerkleExample<WalletUnlocked>, WalletUnlocked) {
// Launch a local network and deploy the contract
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(
Some(1), /* Single wallet */
Some(1), /* Single coin (UTXO) */
Some(1_000_000_000), /* Amount per coin */
),
None,
None,
)
.await
.unwrap();
let wallet = wallets.pop().unwrap();
let id = Contract::load_from(
"./merkle/out/release/merkle_examples.bin",
LoadConfiguration::default(),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = MerkleExample::new(id, wallet.clone());
(instance, wallet)
}
#[tokio::test]
async fn rust_setup_example() {
let (contract_instance, _id) = get_contract_instance().await;
// ANCHOR: generating_a_tree
// Create a new Merkle Tree and define leaves
let mut tree = MerkleTree::new();
let leaves = [b"A", b"B", b"C"].to_vec();
// Hash the leaves and then push to the merkle tree
for datum in &leaves {
let mut hasher = Sha256::new();
hasher.update(datum);
let hash = hasher.finalize();
tree.push(&hash);
}
// ANCHOR_END: generating_a_tree
// ANCHOR: generating_proof
// Define the key or index of the leaf you want to prove and the number of leaves
let key: u64 = 0;
// Get the merkle root and proof set
let (merkle_root, proof_set) = tree.prove(key).unwrap();
// Convert the proof set from Vec<Bytes32> to Vec<Bits256>
let mut bits256_proof: Vec<Bits256> = Vec::new();
for itterator in proof_set {
bits256_proof.push(Bits256(itterator));
}
// ANCHOR_END: generating_proof
// ANCHOR: verify_proof
// Create the merkle leaf
let mut leaf_hasher = Sha256::new();
leaf_hasher.update(leaves[key as usize]);
let hashed_leaf_data = leaf_hasher.finalize();
let merkle_leaf = leaf_sum(&hashed_leaf_data);
// Get the number of leaves or data points
let num_leaves: u64 = leaves.len() as u64;
// Call the Sway contract to verify the generated merkle proof
let result: bool = contract_instance
.methods()
.verify(
Bits256(merkle_root),
key,
Bits256(merkle_leaf),
num_leaves,
bits256_proof,
)
.call()
.await
.unwrap()
.value;
assert!(result);
// ANCHOR_END: verify_proof
}
pub fn leaf_sum(data: &[u8]) -> [u8; 32] {
let mut hash = Sha256::new();
hash.update([LEAF]);
hash.update(data);
hash.finalize().into()
}
Using Fuel-Merkle
Generating A Tree
To create a merkle tree using Fuel-Merkle is as simple as pushing your leaves in increasing order.
// ANCHOR: import
use fuel_merkle::binary::in_memory::MerkleTree;
// ANCHOR_END: import
use fuels::{prelude::*, types::Bits256};
use sha2::{Digest, Sha256};
pub const LEAF: u8 = 0x00;
// Load abi from json
abigen!(Contract(
name = "MerkleExample",
abi = "merkle/out/release/merkle_examples-abi.json"
));
async fn get_contract_instance() -> (MerkleExample<WalletUnlocked>, WalletUnlocked) {
// Launch a local network and deploy the contract
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(
Some(1), /* Single wallet */
Some(1), /* Single coin (UTXO) */
Some(1_000_000_000), /* Amount per coin */
),
None,
None,
)
.await
.unwrap();
let wallet = wallets.pop().unwrap();
let id = Contract::load_from(
"./merkle/out/release/merkle_examples.bin",
LoadConfiguration::default(),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = MerkleExample::new(id, wallet.clone());
(instance, wallet)
}
#[tokio::test]
async fn rust_setup_example() {
let (contract_instance, _id) = get_contract_instance().await;
// ANCHOR: generating_a_tree
// Create a new Merkle Tree and define leaves
let mut tree = MerkleTree::new();
let leaves = [b"A", b"B", b"C"].to_vec();
// Hash the leaves and then push to the merkle tree
for datum in &leaves {
let mut hasher = Sha256::new();
hasher.update(datum);
let hash = hasher.finalize();
tree.push(&hash);
}
// ANCHOR_END: generating_a_tree
// ANCHOR: generating_proof
// Define the key or index of the leaf you want to prove and the number of leaves
let key: u64 = 0;
// Get the merkle root and proof set
let (merkle_root, proof_set) = tree.prove(key).unwrap();
// Convert the proof set from Vec<Bytes32> to Vec<Bits256>
let mut bits256_proof: Vec<Bits256> = Vec::new();
for itterator in proof_set {
bits256_proof.push(Bits256(itterator));
}
// ANCHOR_END: generating_proof
// ANCHOR: verify_proof
// Create the merkle leaf
let mut leaf_hasher = Sha256::new();
leaf_hasher.update(leaves[key as usize]);
let hashed_leaf_data = leaf_hasher.finalize();
let merkle_leaf = leaf_sum(&hashed_leaf_data);
// Get the number of leaves or data points
let num_leaves: u64 = leaves.len() as u64;
// Call the Sway contract to verify the generated merkle proof
let result: bool = contract_instance
.methods()
.verify(
Bits256(merkle_root),
key,
Bits256(merkle_leaf),
num_leaves,
bits256_proof,
)
.call()
.await
.unwrap()
.value;
assert!(result);
// ANCHOR_END: verify_proof
}
pub fn leaf_sum(data: &[u8]) -> [u8; 32] {
let mut hash = Sha256::new();
hash.update([LEAF]);
hash.update(data);
hash.finalize().into()
}
Generating And Verifying A Proof
To generate a proof for a specific leaf, you must have the index or key of the leaf. Simply call the prove function:
// ANCHOR: import
use fuel_merkle::binary::in_memory::MerkleTree;
// ANCHOR_END: import
use fuels::{prelude::*, types::Bits256};
use sha2::{Digest, Sha256};
pub const LEAF: u8 = 0x00;
// Load abi from json
abigen!(Contract(
name = "MerkleExample",
abi = "merkle/out/release/merkle_examples-abi.json"
));
async fn get_contract_instance() -> (MerkleExample<WalletUnlocked>, WalletUnlocked) {
// Launch a local network and deploy the contract
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(
Some(1), /* Single wallet */
Some(1), /* Single coin (UTXO) */
Some(1_000_000_000), /* Amount per coin */
),
None,
None,
)
.await
.unwrap();
let wallet = wallets.pop().unwrap();
let id = Contract::load_from(
"./merkle/out/release/merkle_examples.bin",
LoadConfiguration::default(),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = MerkleExample::new(id, wallet.clone());
(instance, wallet)
}
#[tokio::test]
async fn rust_setup_example() {
let (contract_instance, _id) = get_contract_instance().await;
// ANCHOR: generating_a_tree
// Create a new Merkle Tree and define leaves
let mut tree = MerkleTree::new();
let leaves = [b"A", b"B", b"C"].to_vec();
// Hash the leaves and then push to the merkle tree
for datum in &leaves {
let mut hasher = Sha256::new();
hasher.update(datum);
let hash = hasher.finalize();
tree.push(&hash);
}
// ANCHOR_END: generating_a_tree
// ANCHOR: generating_proof
// Define the key or index of the leaf you want to prove and the number of leaves
let key: u64 = 0;
// Get the merkle root and proof set
let (merkle_root, proof_set) = tree.prove(key).unwrap();
// Convert the proof set from Vec<Bytes32> to Vec<Bits256>
let mut bits256_proof: Vec<Bits256> = Vec::new();
for itterator in proof_set {
bits256_proof.push(Bits256(itterator));
}
// ANCHOR_END: generating_proof
// ANCHOR: verify_proof
// Create the merkle leaf
let mut leaf_hasher = Sha256::new();
leaf_hasher.update(leaves[key as usize]);
let hashed_leaf_data = leaf_hasher.finalize();
let merkle_leaf = leaf_sum(&hashed_leaf_data);
// Get the number of leaves or data points
let num_leaves: u64 = leaves.len() as u64;
// Call the Sway contract to verify the generated merkle proof
let result: bool = contract_instance
.methods()
.verify(
Bits256(merkle_root),
key,
Bits256(merkle_leaf),
num_leaves,
bits256_proof,
)
.call()
.await
.unwrap()
.value;
assert!(result);
// ANCHOR_END: verify_proof
}
pub fn leaf_sum(data: &[u8]) -> [u8; 32] {
let mut hash = Sha256::new();
hash.update([LEAF]);
hash.update(data);
hash.finalize().into()
}
Once the proof has been generated, you may call the Sway Smart Contract's verify_proof function:
// ANCHOR: import
use fuel_merkle::binary::in_memory::MerkleTree;
// ANCHOR_END: import
use fuels::{prelude::*, types::Bits256};
use sha2::{Digest, Sha256};
pub const LEAF: u8 = 0x00;
// Load abi from json
abigen!(Contract(
name = "MerkleExample",
abi = "merkle/out/release/merkle_examples-abi.json"
));
async fn get_contract_instance() -> (MerkleExample<WalletUnlocked>, WalletUnlocked) {
// Launch a local network and deploy the contract
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(
Some(1), /* Single wallet */
Some(1), /* Single coin (UTXO) */
Some(1_000_000_000), /* Amount per coin */
),
None,
None,
)
.await
.unwrap();
let wallet = wallets.pop().unwrap();
let id = Contract::load_from(
"./merkle/out/release/merkle_examples.bin",
LoadConfiguration::default(),
)
.unwrap()
.deploy(&wallet, TxPolicies::default())
.await
.unwrap();
let instance = MerkleExample::new(id, wallet.clone());
(instance, wallet)
}
#[tokio::test]
async fn rust_setup_example() {
let (contract_instance, _id) = get_contract_instance().await;
// ANCHOR: generating_a_tree
// Create a new Merkle Tree and define leaves
let mut tree = MerkleTree::new();
let leaves = [b"A", b"B", b"C"].to_vec();
// Hash the leaves and then push to the merkle tree
for datum in &leaves {
let mut hasher = Sha256::new();
hasher.update(datum);
let hash = hasher.finalize();
tree.push(&hash);
}
// ANCHOR_END: generating_a_tree
// ANCHOR: generating_proof
// Define the key or index of the leaf you want to prove and the number of leaves
let key: u64 = 0;
// Get the merkle root and proof set
let (merkle_root, proof_set) = tree.prove(key).unwrap();
// Convert the proof set from Vec<Bytes32> to Vec<Bits256>
let mut bits256_proof: Vec<Bits256> = Vec::new();
for itterator in proof_set {
bits256_proof.push(Bits256(itterator));
}
// ANCHOR_END: generating_proof
// ANCHOR: verify_proof
// Create the merkle leaf
let mut leaf_hasher = Sha256::new();
leaf_hasher.update(leaves[key as usize]);
let hashed_leaf_data = leaf_hasher.finalize();
let merkle_leaf = leaf_sum(&hashed_leaf_data);
// Get the number of leaves or data points
let num_leaves: u64 = leaves.len() as u64;
// Call the Sway contract to verify the generated merkle proof
let result: bool = contract_instance
.methods()
.verify(
Bits256(merkle_root),
key,
Bits256(merkle_leaf),
num_leaves,
bits256_proof,
)
.call()
.await
.unwrap()
.value;
assert!(result);
// ANCHOR_END: verify_proof
}
pub fn leaf_sum(data: &[u8]) -> [u8; 32] {
let mut hash = Sha256::new();
hash.update([LEAF]);
hash.update(data);
hash.finalize().into()
}
Signed Integers Library
The Signed Integers library provides a library to use signed numbers in Sway. It has 6 distinct types: I8, I16, I32, I64, I128, I256. These types are stack allocated.
Internally the library uses the u8, u16, u32, u64, U128, u256 types to represent the underlying values of the signed integers.
For implementation details on the Signed Integers Library please see the Sway Libs Docs.
Importing the Signed Integer Library
In order to use the Signed Integer Number Library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Signed Integer Number Library to your Sway Smart Contract, add the following to your Sway file:
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
In order to use any of the Signed Integer types, import them into your Sway project like so:
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Basic Functionality
All the functionality is demonstrated with the I8 type, but all of the same functionality is available for the other types as well.
Instantiating a Signed Integer
Zero value
Once imported, a Signed Integer type can be instantiated defining a new variable and calling the new function.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
this newly initialized variable represents the value of 0.
The new function is functionally equivalent to the zero function.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Positive and Negative Values
As the signed variants can only represent half as high a number as the unsigned variants (but with either a positive or negative sign), the try_from and neg_try_from functions will only work with half of the maximum value of the unsigned variant.
You can use the try_from function to create a new positive Signed Integer from a its unsigned variant.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
You can use the neg_try_from function to create a new negative Signed Integer from a its unsigned variant.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
With underlying value
As mentioned previously, the signed integers are internally represented by an unsigned integer, with its values divided into two halves, the bottom half of the values represent the negative values and the top half represent the positive values, and the middle value represents zero.
Therefore, for the lowest value representable by a i8, -128, the underlying value would be 0.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
For the zero value, the underlying value would be 128.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
And for the highest value representable by a i8, 127, the underlying value would be 255.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Minimum and Maximum Values
To get the minimum and maximum values of a signed integer, use the min and max functions.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Basic Mathematical Functions
Basic arithmetic operations are working as usual.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Checking if a Signed Integer is Zero
The library also provides a helper function to easily check if a Signed Integer is zero.
library;
// ANCHOR: import
use sway_libs::signed_integers::*;
// ANCHOR_END: import
// ANCHOR: import_8
use sway_libs::signed_integers::i8::I8;
// ANCHOR_END: import_8
fn initialize() {
// ANCHOR: initialize
let mut i8_value = I8::new();
// ANCHOR_END: initialize
// ANCHOR: zero
let zero = I8::zero();
// ANCHOR_END: zero
// ANCHOR: zero_from_underlying
let zero = I8::from_uint(128u8);
// ANCHOR_END: zero_from_underlying
// ANCHOR: neg_128_from_underlying
let neg_128 = I8::from_uint(0u8);
// ANCHOR_END: neg_128_from_underlying
// ANCHOR: 127_from_underlying
let pos_127 = I8::from_uint(255u8);
// ANCHOR_END: 127_from_underlying
// ANCHOR: min
let min = I8::min();
// ANCHOR_END: min
// ANCHOR: max
let max = I8::max();
// ANCHOR_END: max
}
fn conversion() {
// ANCHOR: positive_conversion
let one = I8::try_from(1u8).unwrap();
// ANCHOR_END: positive_conversion
// ANCHOR: negative_conversion
let negative_one = I8::neg_try_from(1u8).unwrap();
// ANCHOR_END: negative_conversion
}
// ANCHOR: mathematical_ops
fn add_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 + val2;
}
fn subtract_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 - val2;
}
fn multiply_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 * val2;
}
fn divide_signed_int(val1: I8, val2: I8) {
let result: I8 = val1 / val2;
}
// ANCHOR_END: mathematical_ops
// ANCHOR: is_zero
fn is_zero() {
let i8 = I8::zero();
assert(i8.is_zero());
}
// ANCHOR_END: is_zero
Known Issues
The current implementation of U128 will compile large bytecode sizes when performing mathematical computations. As a result, I128 and I256 inherit the same issue and could cause high transaction costs. This should be resolved with future optimizations of the Sway compiler.
Queue Library
A Queue is a linear structure which follows the First-In-First-Out (FIFO) principle. This means that the elements added first are the ones that get removed first.
For implementation details on the Queue Library please see the Sway Libs Docs.
Importing the Queue Library
In order to use the Queue Library, Sway Libs must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started.
To import the Queue Library to your Sway Smart Contract, add the following to your Sway file:
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Basic Functionality
Instantiating a New Queue
Once the Queue has been imported, you can create a new queue instance by calling the new function.
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Enqueuing elements
Adding elements to the Queue can be done using the enqueue function.
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Dequeuing Elements
To remove elements from the Queue, the dequeue function is used. This function follows the FIFO principle.
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Fetching the Head Element
To retrieve the element at the head of the Queue without removing it, you can use the peek function.
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Checking the Queue's Length
The is_empty and len functions can be used to check if the queue is empty and to get the number of elements in the queue respectively.
library;
// ANCHOR: import
use sway_libs::queue::*;
// ANCHOR_END: import
fn instantiate() {
// ANCHOR: instantiate
let mut queue = Queue::new();
// ANCHOR_END: instantiate
}
fn enqueue() {
let mut queue = Queue::new();
// ANCHOR: enqueue
// Enqueue an element to the queue
queue.enqueue(10u8);
// ANCHOR_END: enqueue
}
fn dequeue() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: dequeue
// Dequeue the first element and unwrap the value
let first_item = queue.dequeue().unwrap();
// ANCHOR_END: dequeue
}
fn peek() {
let mut queue = Queue::new();
queue.enqueue(10u8);
// ANCHOR: peek
// Peek at the head of the queue
let head_item = queue.peek();
// ANCHOR_END: peek
}
fn length() {
let mut queue = Queue::new();
// ANCHOR: length
// Checks if queue is empty (returns True or False)
let is_queue_empty = queue.is_empty();
// Returns length of queue
let queue_length = queue.len();
// ANCHOR_END: length
}
Upgradability Library
The Upgradability Library provides functions that can be used to implement contract upgrades via simple upgradable proxies. The Upgradability Library implements the required and optional functionality from SRC-14 as well as additional functionality for ownership of the proxy contract.
For implementation details on the Upgradability Library please see the Sway Libs Docs.
Importing the Upgradability Library
In order to use the Upgradability library, Sway Libs and Sway Standards must be added to the Forc.toml file and then imported into your Sway project. To add Sway Libs as a dependency to the Forc.toml file in your project please see the Getting Started. To add Sway Standards as a dependency please see the Sway Standards Book.
To import the Upgradability Library and SRC-14 Standard to your Sway Smart Contract, add the following to your Sway file:
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
Integrating the Upgradability Library into the SRC-14 Standard
To implement the SRC-14 standard with the Upgradability library, be sure to add the Sway Standards dependency to your contract. The following demonstrates the integration of the Ownership library with the SRC-14 standard.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
NOTE An initialization method must be implemented to initialize the proxy target or proxy owner.
Basic Functionality
Setting and getting a Proxy Target
Once imported, the Upgradability Library's functions will be available. Use them to change the proxy target for your contract by calling the set_proxy_target() function.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
Use the proxy_target() method to get the current proxy target.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
Setting and getting a Proxy Owner
To change the proxy target for your contract use the set_proxy_owner() function.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
Use the proxy_owner() method to get the current proxy owner.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
Proxy access control
To restrict a function to only be callable by the proxy's owner, call the only_proxy_owner() function.
contract;
// ANCHOR: import
use sway_libs::upgradability::*;
use standards::{src14::*, src5::*};
// ANCHOR_END: import
// ANCHOR: integrate_with_src14
use sway_libs::upgradability::{_proxy_owner, _proxy_target, _set_proxy_target};
use standards::{src14::{SRC14, SRC14Extension}, src5::State};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: Option<ContractId> = None,
/// The [State] of the proxy owner.
///
/// # Additional Information
///
/// `proxy_owner` is stored at sha256("storage_SRC14_1")
proxy_owner in 0xbb79927b15d9259ea316f2ecb2297d6cc8851888a98278c0a2e03e1a091ea754: State = State::Uninitialized,
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
}
// ANCHOR_END: integrate_with_src14
// ANCHOR: set_proxy_target
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
_set_proxy_target(new_target);
}
// ANCHOR_END: set_proxy_target
// ANCHOR: proxy_target
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
_proxy_target()
}
// ANCHOR_END: proxy_target
// ANCHOR: set_proxy_owner
#[storage(write)]
fn set_proxy_owner(new_proxy_owner: State) {
_set_proxy_owner(new_proxy_owner);
}
// ANCHOR_END: set_proxy_owner
// ANCHOR: proxy_owner
#[storage(read)]
fn proxy_owner() -> State {
_proxy_owner()
}
// ANCHOR_END: proxy_owner
// ANCHOR: only_proxy_owner
#[storage(read)]
fn only_proxy_owner_may_call() {
only_proxy_owner();
// Only the proxy's owner may reach this line.
}
// ANCHOR_END: only_proxy_owner
SRC-2: Inline Documentation
The following standard intends to define the structure and organization of inline documentation for functions, structs, enums, storage, configurables, and more within the Sway Language. This is a living standard.
Motivation
The standard seeks to provide a better developer experience using Fuel's tooling and the Language Server. This will allow for better interoperability between applications and enable developers to quickly understand any external code they are using or implementing.
Prior Art
A number of pre-existing functions in the sway standard library, sway-applications, and sway-libs repositories have inline documentation. The inline documentation for these is already compatible with Fuel's VS Code extension. These however do not all follow the same structure and outline.
Specification
Functions
The following describes the structure and order of inline documentation for functions. Some sections MAY NOT apply to each function. When a section is not relevant it SHALL be omitted.
Functions: Description
This section has no header. A simple explanation of the function's intent or functionality. Example:
/// This function computes the hash of two numbers.
Functions: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the function's intent or functionality.
Example:
/// # Additional Information
///
/// This function also has some complex behaviors.
Functions: Arguments
This section has a h1 header.
Lists the arguments of the function's definition with the * symbol and describes each one. The list SHALL provide the name, type, and description. The argument SHALL be encapsulated between two backticks: argument. The type SHALL be encapsulated between two square brackets: [type].
Example:
/// # Arguments
///
/// * `argument_1`: [Identity] - This argument is a user to be hashed.
Functions: Returns
This section has a h1 header.
Lists the return values of the function with the * symbol and describes each one. This list SHALL be in the order of the return index and provide the type and description. The type SHALL be encapsulated between two square brackets: [type].
Example:
/// # Returns
///
/// * [u64] - The number of hashes performed.
Functions: Reverts
This section has a h1 header.
Lists the cases in which the function will revert starting with the * symbol. The list SHALL be in the order of occurrence within the function.
Example:
/// # Reverts
///
/// * When `argument_1` or `argument_2` are a zero [b256].
Functions: Number of Storage Accesses
This section has a h1 header.
Provides information on how many storage reads, writes, and clears occur within the function.
Example:
/// # Number of Storage Accesses
///
/// * Reads: `1`
/// * Clears: `2`
Functions: Examples
This section has a h1 header.
This section provides an example of the use of the function. This section is not required to follow the SRC-2 standard however encouraged for auxiliary and library functions.
Example:
/// # Examples
///
/// ```sway
/// fn foo(argument_1: b256, argument_2: b256) {
/// let result = my_function(argument_1, argument_2);
/// }
Structs
The following describes the structure and order of inline documentation for structs. Some sections MAY NOT apply to each struct. When a section is not relevant it SHALL be omitted.
Structs: Description
This section has no header. A simple explanation of the struct's purpose or functionality. Example:
/// This struct contains information on an NFT.
Structs: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the struct's purpose or functionality.
Example:
/// # Additional Information
///
/// This struct also has some complex behaviors.
Fields
The following describes the structure and order of inline documentation for fields within structs. Some sections MAY NOT apply to each field. When a section is not relevant it SHALL be omitted.
Fields: Description
This section has no header. Each field SHALL have its own description with a simple explanation of the field's purpose or functionality. Example:
/// This field represents an owner.
field_1: Identity,
Fields: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the field's purpose or functionality.
Example:
/// # Additional Information
///
/// This field also has some complex behaviors.
Enums
The following describes the structure and order of inline documentation for enums. Some sections MAY NOT apply to each enum. When a section is not relevant it SHALL be omitted.
Enums: Description
This section has no header. A simple explanation of the enum's purpose or functionality. Example:
/// This enum holds the state of a contract.
Enums: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the enum's purpose or functionality.
Example:
/// # Additional Information
///
/// This enum also has some complex behaviors.
Variant
The following describes the structure and order of inline documentation for fields within enums. Some sections MAY NOT apply to each field. When a section is not relevant it SHALL be omitted.
Variant: Description
This section has no header. Each variant SHALL have its own description with a simple explanation of the variant's purpose or functionality. Example:
/// This variant represents the uninitialized state of a contract.
variant_1: (),
/// This variant represents the initialized state of a contract.
variant_2: Identity,
Variant: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the variant's purpose or functionality.
Example:
/// # Additional Information
///
/// This variant also has some complex behaviors.
Errors
In Sway, errors are recommended to be enums. They SHALL follow the same structure and order for inline documentation as described above for enums. Some sections MAY NOT apply to each error. When a section is not relevant it SHALL be omitted.
Logs
In Sway, logs are recommended to be structs. They SHALL follow the same structure and order for inline documentation as described above for structs. Some sections MAY NOT apply to each log. When a section is not relevant it SHALL be omitted.
Storage
The following describes the structure and order of inline documentation for variables within the storage block. Some sections MAY NOT apply to each storage variable. When a section is not relevant it SHALL be omitted.
Storage: Description
This section has no header. A simple explanation of the storage variable's purpose or functionality. Example:
/// This storage variable is used for state.
Storage: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the storage variable's purpose or functionality.
Example:
/// # Additional Information
///
/// This storage variable maps a user to a state.
Configurable
The following describes the structure and order of inline documentation for variables in the configurable block. Some sections MAY NOT apply to each storage variable. When a section is not relevant it SHALL be omitted.
Configurable: Description
This section has no header. A simple explanation of the configurable variable's purpose or functionality. Example:
/// This configurable variable is used for an address.
Configurable: Additional Information
This section has a h1 header.
This section is directly below the description and can provide additional information beyond the configurable variable's purpose or functionality.
Example:
/// # Additional Information
///
/// This configurable variable makes security assumptions.
Other Sections
If the above described sections are not relevant for the information that needs to documented, a custom section with a arbitrary h1 header may be utilized.
Example:
/// # Recommended Message Style
///
/// We recommend that `expect` messages are used to describe the reason you *expect* the `Option` should be `Some`.
Rationale
The SRC-2 standard should help provide developers with an easy way to both quickly write inline documentation and get up to speed on other developers' code. This standard in combination with Fuel's VS Code extension provides readily accessible information on functions, structs, and enums
Backwards Compatibility
There are no standards that the SRC-2 standard requires to be backward compatible with.
Security Considerations
This standard will improve security by providing developers with relevant information such as revert cases.
Examples
Function Example
/// Ensures that the sender is the owner.
///
/// # Arguments
///
/// * `number`: [u64] - A value that is checked to be 5.
///
/// # Returns
///
/// * [bool] - Determines whether `number` is or is not 5.
///
/// # Reverts
///
/// * When the sender is not the owner.
///
/// # Number of Storage Accesses
///
/// * Reads: `1`
///
/// # Examples
///
/// ```sway
/// use ownable::Ownership;
///
/// storage {
/// owner: Ownership = Ownership::initialized(Identity::Address(Address::zero())),
/// }
///
/// fn foo() {
/// storage.owner.only_owner();
/// // Do stuff here
/// }
#[storage(read)]
pub fn only_owner(self, number: u64) -> bool {
require(self.owner() == State::Initialized(msg_sender().unwrap()), AccessError::NotOwner);
number == 5
}
Struct Examples
/// Metadata that is tied to an asset.
pub struct NFTMetadata {
/// Represents the ID of this NFT.
value: u64,
}
/// Log of a bid.
pub struct Bid {
/// The number of coins that were bid.
amount: u64,
/// The user which placed this bid.
bidder: Identity,
}
Enum Examples
/// Determines the state of ownership.
pub enum State {
/// The ownership has not been set.
Uninitialized: (),
/// The user who has been given ownership.
Initialized: Identity,
/// The ownership has been given up and can never be set again.
Revoked: (),
}
/// Error log for when access is denied.
pub enum AccessError {
/// Emitted when the caller is not the owner of the contract.
NotOwner: (),
}
Storage Examples
storage {
/// An asset which is to be distributed.
asset: Option<AssetId> = Option::None,
/// Stores the ClaimState of users that have interacted with the Airdrop Distributor contract.
///
/// # Additional Information
///
/// Maps (user => claim)
claims: StorageMap<Identity, ClaimState> = StorageMap {},
}
Configurable Example
configurable {
/// The threshold required for activation.
THRESHOLD: u64 = 5,
}
SRC-3: Minting and Burning Native Assets
The following standard enables the minting and burning of native assets for any fungible assets within the Sway Language. It seeks to define mint and burn functions defined separately from the SRC-20 standard.
Motivation
The intent of this standard is to separate the extensions of minting and burning from the SRC-20 standard.
Prior Art
Minting and burning were initially added to the SRC-20 standard.
Specification
Required Public Functions
The following functions MUST be implemented to follow the SRC-3 standard:
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64)
This function MUST mint amount coins with a sub-identifier and transfer them to the recipient.
This function MUST use the sub_id as the sub-identifier IF sub_id is Some, otherwise this function MUST assign a SubId if the sub_id argument is None.
This function MAY contain arbitrary conditions for minting, and revert if those conditions are not met.
Mint Arguments
recipient- TheIdentityto which the newly minted asset is transferred to.sub_id- The sub-identifier of the asset to mint. If this isNone, aSubIdMUST be assigned.amount- The quantity of coins to mint.
fn burn(sub_id: SubId, amount: u64)
This function MUST burn amount coins with the sub-identifier sub_id and MUST ensure the AssetId of the asset is the sha-256 hash of (ContractId, SubId) for the implementing contract.
This function MUST ensure at least amount coins have been transferred to the implementing contract.
This function MUST update the total supply defined in the SRC-20 standard.
This function MAY contain arbitrary conditions for burning, and revert if those conditions are not met.
Burn Arguments
sub_id- The sub-identifier of the asset to burn.amount- The quantity of coins to burn.
Rationale
This standard has been added to enable compatibility between applications and allow minting and burning native assets per use case. This standard has been separated from the SRC-20 standard to allow for the minting and burning for all fungible assets, irrelevant of whether they are Native Assets or not.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets ensuring its compatibility with the SRC-20 standard.
Security Considerations
This standard may introduce security considerations if no checks are implemented to ensure the calling of the mint() function is deemed valid or permitted. Checks are highly encouraged.
The burn function may also introduce a security consideration if the total supply within the SRC-20 standard is not modified.
Example ABI
abi SRC3 {
#[storage(read, write)]
fn mint(recipient: Identity, sub_id: Option<SubId>, amount: u64);
#[payable]
#[storage(read, write)]
fn burn(sub_id: SubId, amount: u64);
}
Example Implementation
Single Native Asset
Example of the SRC-3 implementation where a contract only mints a single asset with one SubId.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src3-mint-burn/single_asset/src/single_asset.sw' -->
Multi Native Asset
Example of the SRC-3 implementation where a contract mints multiple assets with differing SubId values.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src3-mint-burn/multi_asset/src/multi_asset.sw' -->
SRC-5: Ownership
The following standard intends to enable the use of administrators or owners in Sway contracts.
Motivation
The standard seeks to provide a method for restricting access to particular users within a Sway contract.
Prior Art
The sway-libs repository contains a pre-existing Ownership library.
Ownership libraries exist for other ecosystems such as OpenZeppelin's Ownership library.
Specification
State
There SHALL be 3 states for any library implementing an ownership module in the following order:
Uninitialized
The Uninitialized state SHALL be set as the initial state if no owner or admin is set. The Uninitialized state MUST be used when an owner or admin MAY be set in the future.
Initialized
The Initialized state SHALL be set as the state if an owner or admin is set with an associated Identity type.
Revoked
The Revoked state SHALL be set when there is no owner or admin and there SHALL NOT be one set in the future.
Example:
pub enum State {
Uninitialized: (),
Initialized: Identity,
Revoked: (),
}
Functions
The following functions MUST be implemented to follow the SRC-5 standard:
fn owner() -> State
This function SHALL return the current state of ownership for the contract where State is either Uninitialized, Initialized, or Revoked.
Errors
There SHALL be error handling.
NotOwner
This error MUST be emitted when only_owner() reverts.
Rationale
In order to provide a universal method of administrative capabilities, SRC-5 will further enable interoperability between applications and provide safeguards for smart contract security.
Backwards Compatibility
The SRC-5 standard is compatible with the sway-libs repository pre-existing Ownership library. Considerations should be made to best handle multiple owners or admins.
There are no standards that SRC-5 requires to be compatible with.
Security Considerations
The SRC-5 standard should help improve the security of Sway contracts and their interoperability.
Example ABI
abi SRC5 {
#[storage(read)]
fn owner() -> State;
}
Example Implementation
Uninitialized
Example of the SRC-5 implementation where a contract does not have an owner set at compile time with the intent to set it during runtime.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src5-ownership/uninitialized_example/src/uninitialized_example.sw' -->
Initialized
Example of the SRC-5 implementation where a contract has an owner set at compile time.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src5-ownership/initialized_example/src/initialized_example.sw' -->
SRC-6: Vault
The following standard allows for the implementation of a standard API for asset vaults such as yield-bearing asset vaults or asset wrappers. This standard is an optional add-on to the SRC-20 standard.
Motivation
Asset vaults allow users to own shares of variable amounts of assets, such as lending protocols which may have growing assets due to profits from interest. This pattern is highly useful and would greatly benefit from standardization.
Prior Art
Asset vaults have been thoroughly explored on Ethereum and with EIP 4626 they have their own standard for it. However as Fuel's Native Assets are fundamentally different from Ethereum's ERC-20 tokens, the implementation will differ, but the interface may be used as a reference.
Specification
Required public functions
The following functions MUST be implemented to follow the SRC-6 standard. Any contract that implements the SRC-6 standard MUST implement the SRC-20 standard.
fn deposit(receiver: Identity, vault_sub_id: SubId) -> u64
This function takes the receiver Identity and the SubId vault_sub_id of the sub-vault as an argument and returns the amount of shares minted to the receiver.
- This function MUST allow for depositing of the underlying asset in exchange for pro-rata shares of the vault.
- This function MAY reject arbitrary assets based on implementation and MUST revert if unaccepted assets are forwarded.
- This function MAY reject any arbitrary
receiverbased on implementation and MUST revert in the case of a blacklisted or non-whitelistedreceiver. - This function MUST mint an asset representing the pro-rata share of the vault, with the SubId of the
sha256((underlying_asset, vault_sub_id))digest, whereunderlying_assetis theAssetIdof the deposited asset and thevault_sub_idis the id of the vault. - This function MUST emit a
Depositlog. - This function MUST return the amount of minted shares.
fn withdraw(receiver: Identity, underlying_asset: AssetId, vault_sub_id: SubId) -> u64
This function takes the receiver Identity, the underlying_asset AssetId, and the vault_sub_id of the sub vault, as arguments and returns the amount of assets transferred to the receiver.
- This function MUST allow for redeeming of the vault shares in exchange for a pro-rata amount of the underlying assets.
- This function MUST revert if any
AssetIdother than theAssetIdrepresenting the underlying asset's shares for the given sub vault atvault_sub_idis forwarded. (i.e. transferred share'sAssetIdmust be equal toAssetId::new(ContractId::this(), sha256((underlying_asset, vault_sub_id))) - This function MUST burn the received shares.
- This function MUST emit a
Withdrawlog. - This function MUST return amount of assets transferred to the receiver.
fn managed_assets(underlying_asset: AssetId, vault_sub_id: SubId) -> u64
This function returns the total assets under management by vault. Includes assets controlled by the vault but not directly possessed by vault. It takes the underlying_asset AssetId and the vault_sub_id of the sub vault as arguments and returns the total amount of assets of AssetId under management by vault.
- This function MUST return total amount of assets of
underlying_assetAssetIdunder management by vault. - This function MUST return 0 if there are no assets of
underlying_assetAssetIdunder management by vault. - This function MUST NOT revert under any circumstances.
fn max_depositable(receiver: Identity, underlying_asset: AssetId, vault_sub_id: SubId) -> Option<u64>
This is a helper function for getting the maximum amount of assets that can be deposited. It takes the hypothetical receiver Identity, the underlying_asset AssetId, and the vault_sub_id SubId of the sub vault as an arguments and returns the maximum amount of assets that can be deposited into the contract, for the given asset.
- This function MUST return the maximum amount of assets that can be deposited into the contract, for the given
underlying_asset, if the givenvault_sub_idvault exists. - This function MUST return an
Some(amount)if the givenvault_sub_idvault exists. - This function MUST return an
Noneif the givenvault_sub_idvault does not exist. - This function MUST account for both global and user specific limits. For example: if deposits are disabled, even temporarily, MUST return 0.
fn max_withdrawable(receiver: Identity, underlying_asset: AssetId, vault_sub_id: SubId) -> Option<u64>
This is a helper function for getting maximum withdrawable. It takes the hypothetical receiver Identity, the underlying_asset AssetId, and the vault_sub_id SubId of the sub vault as an argument and returns the maximum amount of assets that can be withdrawn from the contract, for the given asset.
- This function MUST return the maximum amount of assets that can be withdrawn from the contract, for the given
underlying_asset, if the givenvault_sub_idvault exists. - This function MUST return an
Some(amount)if the givenvault_sub_idvault exists. - This function MUST return an
Noneif the givenvault_sub_idvault does not exist. - This function MUST account for global limits. For example: if withdrawals are disabled, even temporarily, MUST return 0.
Required logs
The following logs MUST be emitted at the specified occasions.
Deposit
caller has called the deposit() method sending deposited_amount assets of the underlying_asset Asset to the subvault of vault_sub_id, in exchange for minted_shares shares sent to the receiver receiver.
The Deposit struct MUST be logged whenever new shares are minted via the deposit() method.
The Deposit log SHALL have the following fields.
caller: Identity
The caller field MUST represent the Identity which called the deposit function.
receiver: Identity
The receiver field MUST represent the Identity which received the vault shares.
underlying_asset: AssetId
The underlying_asset field MUST represent the AssetId of the asset which was deposited into the vault.
vault_sub_id: SubId
The vault_sub_id field MUST represent the SubId of the vault which was deposited into.
deposited_amount: u64
The deposited_amount field MUST represent the u64 amount of assets deposited into the vault.
minted_shares: u64
The minted_shares field MUST represent the u64 amount of shares minted.
Withdraw
caller has called the withdraw() method sending burned_shares shares in exchange for withdrawn_amount assets of the underlying_asset Asset from the subvault of vault_sub_id to the receiver receiver.
The Withdraw struct MUST be logged whenever shares are redeemed for assets via the withdraw() method.
The Withdraw log SHALL have the following fields.
caller: Identity
The caller field MUST represent the Identity which called the withdraw function.
receiver: Identity
The receiver field MUST represent the Identity which received the withdrawn assets.
underlying_asset: AssetId
The underlying_asset field MUST represent the AssetId of the asset that was withdrawn.
vault_sub_id: SubId
The vault_sub_id field MUST represent the SubId of the vault from which was withdrawn.
withdrawn_amount: u64
The withdrawn_amount field MUST represent the u64 amount of coins withdrawn.
burned_shares: u64
The burned_shares field MUST represent the u64 amount of shares burned.
Rationale
The ABI discussed covers the known use cases of asset vaults while allowing safe implementations.
Backwards Compatibility
This standard is fully compatible with the SRC-20 standard.
Security Considerations
Incorrect implementation of asset vaults could allow attackers to steal underlying assets. It is recommended to properly audit any code using this standard to ensure exploits are not possible.
Example ABI
abi SRC6 {
#[payable]
#[storage(read, write)]
fn deposit(receiver: Identity, vault_sub_id: SubId) -> u64;
#[payable]
#[storage(read, write)]
fn withdraw(receiver: Identity, underlying_asset: AssetId, vault_sub_id: SubId) -> u64;
#[storage(read)]
fn managed_assets(underlying_asset: AssetId, vault_sub_id: SubId) -> u64;
#[storage(read)]
fn max_depositable(receiver: Identity, underlying_asset: AssetId, vault_sub_id: SubId) -> Option<u64>;
#[storage(read)]
fn max_withdrawable(underlying_asset: AssetId, vault_sub_id: SubId) -> Option<u64>;
}
Example Implementation
Multi Asset Vault
A basic implementation of the vault standard that supports any number of sub vaults being created for every AssetId.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src6-vault/multi_asset_vault/src/main.sw' -->
Single Asset Vault
A basic implementation of the vault standard demonstrating how to restrict deposits and withdrawals to a single AssetId.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src6-vault/single_asset_vault/src/main.sw' -->
Single Asset Single Sub Vault
A basic implementation of the vault standard demonstrating how to restrict deposits and withdrawals to a single AssetId, and to a single Sub vault.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src6-vault/single_asset_single_sub_vault/src/main.sw' -->
SRC-7: Onchain Native Asset Metadata
The following standard attempts to define the retrieval of on-chain arbitrary metadata for any Native Asset. This standard should be used if a stateful approach is needed. Any contract that implements the SRC-7 standard MUST implement the SRC-20 standard.
Motivation
The SRC-7 standard seeks to enable stateful data-rich assets on the Fuel Network while maintaining compatibility between multiple assets minted by the same contract. The standard ensures type safety with the use of an enum and an Option. All metadata queries are done through a single function to facilitate cross-contract calls.
Prior Art
The use of generic metadata was originally found in the Sway-Lib's NFT Library which did not use Fuel's Native Assets. This library has since been deprecated.
A previous definition for a metadata standard was written in the original edit of the now defunct SRC-721. This has since been replaced with the SRC-20 standard as SubId was introduced to enable multiple assets to be minted from a single contract.
The standard takes inspiration from ENS's public resolver with the use of a String as the key. This should enable human-readable keys to help minimize errors and enable the standardization of certain keys, such as "image" as opposed to an enum or u64 representation of keys.
We also take a look at existing common metadata practices such as OpenSea's Metadata Standards and seek to stay backwards compatible with them while enabling more functionality. Through the combination of String keys and various return types, both pre-defined URIs or specific attributes may be stored and retrieved with the SRC-7 standard.
Specification
Metadata Type
The following describes an enum that wraps various metadata types into a single return type. There SHALL be the following variants in the Metadata enum:
B256
The B256 variant SHALL be used when the stored metadata for the corresponding AssetId and Sting key pair is of the b256 type.
Bytes
The Bytes variant SHALL be used when the stored metadata for the corresponding AssetId and String key pair is of the Bytes type. The Bytes variant should be used when storing custom data such as but not limited to structs and enums.
Int
The Int variant SHALL be used when the stored metadata for the corresponding AssetId and Sting key pair is of the u64 type.
String
The String variant SHALL be used when the stored metadata for the corresponding AssetId and String key pair is of the String type. The String variant MUST be used when a URI is required but MAY contain any arbitrary String data.
Required Functions
fn metadata(asset: AssetId, key: String) -> Option<Metadata>
This function MUST return valid metadata for the corresponding asset and key, where the data is either a B256, Bytes, Int, or String variant. If the asset does not exist or no metadata exists, the function MUST return None.
Logging
The following logs MUST be implemented and emitted to follow the SRC-7 standard.
- IF a value is updated via a function call, a log MUST be emitted.
- IF a value is embedded in a contract as a constant, configurable, or other manner, an event MUST be emitted at least once.
SetMetadataEvent
The SetMetadataEvent MUST be emitted when the metadata of an asset has updated.
There SHALL be the following fields in the SetMetadataEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdfor the asset that has been updated.metadata: Themetadatafield SHALL be used for the correspondingOption<Metadata>which represents the metadata of the asset.key: Thekeyfield SHALL be used for the correspondingStringwhich represents the key used for storing the metadata.sender: Thesenderfield SHALL be used for the correspondingIdentitywhich made the function call that has updated the metadata of the asset.
Example:
pub struct SetMetadataEvent {
pub asset: AssetId,
pub metadata: Option<Metadata>,
pub key: String,
pub sender: Identity,
}
Rationale
The SRC-7 standard should allow for stateful data-rich assets to interact with one another in a safe manner.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets and the SRC-20 standard. It also maintains compatibility with existing standards in other ecosystems.
Security Considerations
This standard does not introduce any security concerns, as it does not call external contracts, nor does it define any mutations of the contract state.
Example ABI
abi SRC7 {
#[storage(read)]
fn metadata(asset: AssetId, key: String) -> Option<Metadata>;
}
Example Implementation
Single Native Asset
Example of the SRC-7 implementation where metadata exists for only a single asset with one SubId.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src7-metadata/single_asset/src/single_asset.sw' -->
Multi Native Asset
Example of the SRC-7 implementation where metadata exists for multiple assets with differing SubId values.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src7-metadata/multi_asset/src/multi_asset.sw' -->
SRC-8: Bridged Asset
The following standard attempts to define the retrieval of relevant on-chain metadata for any bridged Native Assets. Any contract that implements the SRC-8 standard MUST implement the SRC-7 and SRC-20 standards.
Motivation
The SRC-8 standard seeks to enable relevant data for bridged assets on the Fuel Network. This data includes the origin chain, address, ID, decimals, and any arbitrary data. All metadata queries are done through a single function to facilitate cross-contract calls.
Prior Art
The use of generic metadata for Native Assets is defined in the SRC-7 standard. This standard integrates into the existing SRC-7 standard.
Specification
Asset Creation
The SubId of the asset MUST be the digest of the sha256(origin_chain_id, origin_asset_address, origin_asset_id) hash where:
origin_chain_idis aStringof the chain ID where the asset was originally minted.origin_asset_addressis ab256of the asset's address on the chain where the asset was originally minted.origin_asset_idis ab256of the asset's ID such as an NFT's ID on the chain where the asset was originally minted. IF there is no ID,b256::zero()SHALL be used.
SRC-20 Metadata
Any bridged assets MUST use the name and symbol of the asset on the chain where the asset was originally minted.
SRC-7 Metadata
bridged:chain
The key bridged:chain SHALL return an String variant of the chain ID where the asset was originally minted.
bridged:address
The key bridged:address SHALL return a B256 variant of the asset's address on the chain where the asset was originally minted. Native assets of a chain that do not have an address such as Ether on Ethereum SHALL use b256::zero().
bridged:id
The key bridged:id MAY return a B256 variant of the asset's ID such as an NFT's ID on the chain where the asset was originally minted. IF there is no ID, None SHALL be returned.
bridged:decimals
The key bridged:decimals MAY return an Int variant of the asset's decimals on the chain where the asset was originally minted. IF there are no decimals, None SHALL be returned.
Rationale
The SRC-8 standard should allow for data on any bridged assets on the Fuel Network. This standard builds off existing standards and should allow other contracts to query any relevant information on the bridged asset.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets, the SRC-20 standard, and the SRC-7 standard.
The standard is also compatible with both tokens and NFTs native to other ecosystems by introducing a token ID element of the original chain.
Security Considerations
This standard does not call external contracts, nor does it define any mutations of the contract state.
Example
impl SRC7 for Contract {
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
if (asset != AssetId::default()) {
return Option::None;
}
match key {
String::from_ascii_str("bridged:chain") => {
Option::Some(String::from_ascii_str("1"))
},
String::from_ascii_str("bridged:address") => {
let origin_asset_address = b256::zero();
Option::Some(Metadata::B256(origin_asset_address))
},
String::from_ascii_str("bridged:id") => {
let origin_asset_id = b256::zero();
Option::Some(Metadata::B256(origin_asset_id))
},
String::from_ascii_str("bridged:decimals") => {
Option::Some(Metadata::Int(1))
},
_ => Option::None,
}
}
}
impl SRC20 for Contract {
fn total_assets() -> u64 {
1
}
fn total_supply(asset: AssetId) -> Option<u64> {
match asset {
AssetId::default() => Option::Some(1),
_ => Option::None,
}
}
fn name(asset: AssetId) -> Option<String> {
match asset {
AssetId::default() => Option::Some(String::from_ascii_str("Name")),
_ => Option::None,
}
}
fn symbol(asset: AssetId) -> Option<String> {
match asset {
AssetId::default() => Option::Some(String::from_ascii_str("Symbol")),
_ => Option::None,
}
}
fn decimals(asset: AssetId) -> Option<u8> {
match asset {
AssetId::default() => Option::Some(0u8),
_ => Option::None,
}
}
}
SRC-9: Native Asset
The following standard attempts to define the keys of relevant on-chain metadata for any Native Assets. Any contract that implements the SRC-9 standard MUST implement the SRC-7 and SRC-20 standards. This is a living standard where revisions may be made as the ecosystem evolves.
Motivation
The SRC-9 standard seeks to enable relevant data for assets on the Fuel Network. This data may include images, text, contact, or all of the above. All metadata queries are done through a single function to facilitate cross-contract calls.
Prior Art
The use of generic metadata for Native Assets is defined in the SRC-7 standard. This standard integrates into the existing SRC-7 standard.
Specification
The following keys are reserved for the SRC-9 standard. Use of the keys should follow the SRC-9 specification.
All keys SHALL use snake case.
Social
The social prefix SHALL be used for any social media platform and SHALL return usernames.
Any social media metadata keys SHALL follow the following syntax social:site where:
- The
socialkeyword must be prepended to denote this is a social platform - The
sitekeyword must be the website or platform of the social
social:discord
The key social:discord SHALL return a String variant of a username for the Discord platform.
social:facebook
The key social:facebook SHALL return a String variant of a username for the Facebook platform.
social:farcaster
The key social:farcaster SHALL return a String variant of a username for the Farcaster platform.
social:friend.tech
The key social:friend.tech SHALL return a String variant of a username for the Friend.tech platform.
social:github
The key social:github SHALL return a String variant of a username for the Github platform.
social:instagram
The key social:instagram SHALL return a String variant of a username for the Instagram platform.
social:lens
The key social:lens SHALL return a String variant of a username for the Lens Protocol.
social:linkedin
The key social:linkedin SHALL return a String variant of a username for the LinkedIn platform.
social:reddit
The key social:reddit SHALL return a String variant of a username for the Reddit platform.
social:signal
The key social:signal SHALL return a String variant of a username for the Signal platform.
social:telegram
The key social:telegram SHALL return a String variant of a username for the Telegram platform.
social:tiktok
The key social:tiktok SHALL return a String variant of a username for the TikTok platform.
social:x
The key social:x SHALL return a String variant of a username for the X or formerly Twitter platform.
social:wechat
The key social:wechat SHALL return a String variant of a username for the WeChat platform.
social:whatsapp
The key social:whatsapp SHALL return a String variant of a username for the WhatsApp platform.
social:youtube
The key social:youtube SHALL return a String variant of a username for the YouTube platform.
Contact
The contact prefix SHALL be used for any contact information on a particular project's team for an asset.
Any contact information metadata keys SHALL follow the following syntax contract:type where:
- The
contactkeyword must be prepended to denote this is contact information - The
typekeyword must be the method of contact
The key SHALL use snake case.
contact:email
The key contact:email SHALL return a String variant of an email.
contact:mailing
The key contact:mailing SHALL return a String variant of a mailing address. All mailing addresses MUST follow the UPU addressing format.
contact:phone
The key contact:phone SHALL return a String variant of a phone number. All phone numbers SHALL follow the E.164 standard.
contact:company
The key contact:company SHALL return a String variant of a company name.
External Links
The link prefix SHALL be used for any external webpage hyperlink associated with an asset.
Any external webpage metadata keys SHALL follow the following syntax link:site where:
- The
linkkeyword must be prepended to denote this is an external webpage - The
sitekeyword must be an external website
link:home
The key link:home SHALL return a String variant of the asset's project homepage.
link:contact
The key link:contact SHALL return a String variant of the asset's project contact information webpage.
link:docs
The key link:docs SHALL return a String variant of the asset's project documentation webpage.
link:forum
The key link:forum SHALL return a String variant of the asset's project forum webpage.
link:blog
The key link:blog SHALL return a String variant of the asset's project blog.
link:linktree
The key link:linktree SHALL return a String variant of the asset's project linktree information webpage.
Resources
The res prefix SHALL be used for any resources or general information on an asset.
Any resource metadata keys SHALL follow the following syntax rec:type where:
- The
reskeyword must be prepended to denote this is a resource - The
typekeyword must be the type of resource
res:license
The key res:license SHALL return a String variant of the asset's project license.
res:tos
The key res:tos SHALL return a String variant of the asset's project Terms of Service.
res:author
The key res:author SHALL return a String variant of the asset's project author. This MAY be a full name or pseudonym.
res:about
The key res:about SHALL return a String variant about the asset's project up to 2048 characters.
res:description
The key res:description SHALL return a String variant describing the asset's project up to 256 characters.
res:date
The key res:date SHALL return a Int variant of a UNIX timestamp.
res:block
The key res:block SHALL return a Int variant of a block number.
Images
The image prefix SHALL be used for any image files associated with a singular asset.
Any image metadata keys SHALL follow the following syntax image:type where:
- The
imagekeyword must be prepended to denote this is an image - The
typekeyword must be the file type of the image
image:svg
The key image:svg SHALL return a String variant of an SVG image.
image:png
The key image:png SHALL return a String variant of a URI for a PNG image.
image:jpeg
The key image:jpeg SHALL return a String variant of a URI for a JPEG image.
image:webp
The key image:webp SHALL return a String variant of a URI for a WebP image.
image:gif
The key image:gif SHALL return a String variant of a URI for a GIF image.
image:heif
The key image:heif SHALL return a String variant of a URI for a HEIF image.
Video
The video prefix SHALL be used for any video files associated with a singular asset.
Any video metadata keys SHALL follow the following syntax video:type where:
- The
videokeyword must be prepended to denote this is a video - The
typekeyword must be the file type of the video
video:mp4
The key video:mp4 SHALL return a String variant of a URI for an MP4 video.
video:webm
The key video:webm SHALL return a String variant of a URI for a WebM video.
video:m4v
The key video:m4v SHALL return a String variant of a URI for a M4V video.
video:ogv
The key video:ogv SHALL return a String variant of a URI for an OGV video.
video:ogg
The key video:ogg SHALL return a String variant of a URI for an OGG video.
Audio
The audio prefix SHALL be used for any audio files associated with a singular asset.
Any audio metadata keys SHALL follow the following syntax audio:type where:
- The
audiokeyword must be prepended to denote this is audio metadata - The
typekeyword must be the file type of the audio
audio:mp3
The key audio:mp3 SHALL return a String variant of a URI for an MP3 file.
audio:wav
The key audio:wav SHALL return a String variant of a URI for a WAV file.
audio:oga
The key audio:oga SHALL return a String variant of a URI for an OGA file.
Media
The media prefix SHALL be used for any media associated with a particular singular asset.
Any media metadata keys SHALL follow the following syntax media:type where:
- The
mediakeyword must be prepended to denote this is a video - The
typekeyword must be the file type of the media
media:gltf
The key media:gltf SHALL return a String variant of a URI for a glTF file.
media:glb
The key media:glb SHALL return a String variant of a URI for a GLB file.
Logos
The logo prefix SHALL be used for any images associated with a particular asset or project.
Any logo metadata keys SHALL follow the following syntax logo:type where:
- The
logokeyword must be prepended to denote this is a logo - The
typekeyword must be the type of logo
logo:svg
The key logo:svg SHALL return a String variant of an SVG image of a logo.
logo:svg_light
The key logo:svg_light SHALL return a String variant of an SVG image of a logo for light themes.
logo:svg_dark
The key logo:svg_dark SHALL return a String variant of an SVG image of a logo for dark themes.
logo:small_light
The key logo:small_light SHALL return a String variant of a URI for a 32x32 PNG image of a logo for light themes.
logo:small_dark
The key logo:small_dark SHALL return a String variant of a URI for a 32x32 PNG image of a logo for dark themes.
logo:medium_light
The key logo:medium_light SHALL return a String variant of a URI for a 256x256 PNG image of a logo for light themes.
logo:medium_dark
The key logo:medium_dark SHALL return a String variant of a URI for a 256x256 PNG image of a logo for dark themes.
logo:large_light
The key logo:large_light SHALL return a String variant of a URI for a 1024x1024 PNG image of a logo for light themes.
logo:large_dark
The key logo:large_dark SHALL return a String variant of a URI for a 1024x1024 PNG image of a logo for dark themes.
Attributes
The attr prefix SHALL be used for any attributes associated with a singular asset.
Any attribute metadata keys SHALL follow the following syntax attr:type where:
- The
attrkeyword must be prepended to denote this is an attribute - The
typekeyword must be the type of attribute
There are no standardized types of attributes.
Example: attr:eyes.
Rationale
The SRC-9 standard should allow for standardized keys for metadata on the Fuel Network. This standard builds off existing standards and should allow other contracts to query any relevant information on the asset.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets, the SRC-20 standard, and the SRC-7 standard.
Security Considerations
This standard does not call external contracts, nor does it define any mutations of the contract state.
Example
impl SRC7 for Contract {
fn metadata(asset: AssetId, key: String) -> Option<Metadata> {
if (asset != AssetId::default()) {
return Option::None;
}
match key {
String::from_ascii_str("social:x") => {
let social = String::from_ascii_str("fuel_network");
Option::Some(Metadata::String(social))
},
_ => Option::None,
}
}
}
SRC-10: Native Bridge
The following standard allows for the implementation of a standard API for Native Bridges using the Sway Language. The standardized design has the bridge contract send a message to the origin chain to register which token it accepts to prevent a loss of funds.
Motivation
A standard interface for bridges intends to provide a safe and efficient bridge between the settlement or canonical chain and the Fuel Network.
Prior Art
The standard is centered on Fuel’s Bridge Architecture. Fuel's bridge system is built on a message protocol that allows to send (and receive) messages between entities located in two different blockchains.
The following standard takes reference from the FungibleBridge ABI defined in the fuel-bridge repository.
Specification
The following functions MUST be implemented to follow the SRC-10; Native Bridge Standard:
Required Functions
fn process_message(message_index: u64)
The process_message() function accepts incoming deposit messages from the canonical chain and issues the corresponding bridged asset.
- This function MUST parse a message at the given
message_indexindex. - This function SHALL mint an asset that follows the SRC-8; Bridged Asset Standard.
- This function SHALL issue a refund if there is an error in the bridging process.
fn withdraw(to_address: b256)
The withdraw() function accepts and burns a bridged Native Asset on Fuel and sends a message to the bridge contract on the canonical chain to release the originally deposited tokens to the to_address address.
- This function SHALL send a message to the bridge contract to release the bridged tokens to the
to_addressaddress on the canonical chain. - This function MUST ensure the asset's
AssetIdsent in the transaction matches a bridged asset. - This function SHALL burn all coins sent in the transaction.
fn claim_refund(to_address: b256, token_address: b256, token_id: b256, gateway_contract: b256)
The claim_refund() function is called if something goes wrong in the bridging process and an error occurs. It sends a message to the gateway_contract contract on the canonical chain to release the token_address token with token id token_id to the to_address address.
- This function SHALL send a message to the
gateway_contractcontract to release thetoken_addresstoken with idtoken_idto theto_addressaddress on the canonical chain. - This function MUST ensure a refund was issued.
Required Data Types
DepositType
The DepositType enum describes whether the bridged deposit is made to a address, contract, or contract and contains additional metadata. There MUST be the following variants in the DepositType enum:
Address: ()
The Address variant MUST represent when the deposit is made to an address on the Fuel chain.
Contract: ()
The Contract variant MUST represent when the deposit is made to an contract on the Fuel chain.
ContractWithData: ()
The ContractWithData variant MUST represent when the deposit is made to an contract and contains additional metadata for the Fuel chain.
Example Deposit Type
pub enum DepositType {
Address: (),
Contract: (),
ContractWithData: (),
}
DepositMessage
The following describes a struct that encapsulates various deposit message metadata to a single type. There MUST be the following fields in the DepositMessage struct:
amount: u256
The amount field MUST represent the number of tokens.
from: b256
The from field MUST represent the bridging user’s address on the canonical chain.
to: Identity
The to field MUST represent the bridging target destination Address or ContractId on the Fuel Chain.
token_address: b256
The token_address field MUST represent the bridged token's address on the canonical chain.
token_id: b256
The token_id field MUST represent the token's ID on the canonical chain. The b256::zero() MUST be used if this is a fungible token and no token ID exists.
decimals: u8
The decimals field MUST represent the bridged token's decimals on the canonical chain.
deposit_type: DepositType
The deposit_type field MUST represent the type of bridge deposit made on the canonical chain.
Example Deposit Message
pub struct DepositMessage {
pub amount: b256,
pub from: b256,
pub to: Identity,
pub token_address: b256,
pub token_id: b256,
pub decimals: u8,
pub deposit_type: DepositType,
}
MetadataMessage
The following describes a struct that encapsulates the metadata of token on the canonical chain to a single type. There MUST be the following fields in the MetadataMessage struct:
token_address: b256
The token_address field MUST represent the bridged token's address on the canonical chain.
token_id: b256
The token_id field MUST represent the token's ID on the canonical chain. The b256::zero() MUST be used if this is a fungible token and no token ID exists.
name: String
The name field MUST represent the bridged token's name field on the canonical chain.
symbol: String
The symbol field MUST represent the bridged token's symbol field on the canonical chain.
Example Metadata Message
pub struct MetadataMessage {
pub token_address: b256,
pub token_id: b256,
pub name: String,
pub symbol: String,
}
Required Standards
Any contract that implements the SRC-10; Native Bridge Standard MUST implement the SRC-8; Bridged Asset Standard for all bridged assets.
Rationale
The SRC-10; Native Bridge Standard is designed to standardize the native bridge interface between all Fuel instances.
Backwards Compatibility
This standard is compatible with the SRC-20 and SRC-8 standards.
Example ABI
abi SRC10 {
fn process_message(message_index: u64);
fn withdraw(to_address: b256);
fn claim_refund(to_address: b256, token_address: b256, token_id: b256, gateway_contract: b256);
}
SRC-11: Security Information
The following standard allows for contract creators to make communication information readily available to everyone, with the primary purpose of allowing white hat hackers to coordinate a bug-fix or securing of funds.
Motivation
White hat hackers may find bugs or exploits in contracts that they want to report to the project for safeguarding of funds. It is not immediately obvious from a ContractId, who the right person to contact is. This standard aims to make the process of bug reporting as smooth as possible.
Prior Art
The security.txt library for Solana has explored this idea. This standard takes inspiration from the library, with some changes.
Specification
Security Information Type
The following describes the SecurityInformation type.
- The struct MAY contain
NoneforOption<T>type fields, if they are deemed unnecessary. - The struct MUST NOT contain empty
StringorVecfields. - The struct MAY contain a URL or the information directly for the following fields:
project_url,policy,encryption,source_code,auditors,acknowledgments,additional_information. - The struct MUST contain the information directly for the following fields:
name,contact_information,preferred_languages,source_release, andsource_revision. - The struct MUST contain at least one item in the
preferred_languagesfield'sVec, if it is notNone. Furthermore, the string should only contain the ISO 639-1 language code and nothing else. - The struct MUST contain at least one item in the
contact_informationfield'sVec. Furthermore, the string should follow the following format<contact_type>:<contact_information>. Wherecontact_typedescribes the method of contact (e.g.emailordiscord) andcontact_informationdescribes the information needed to contact (e.g.example@example.comor@EXAMPLE).
name: String
The name of the project that the contract is associated with.
project_url: Option<String>
The website URL of the project that the contract is associated with.
contact_information: Vec<String>
A list of contact information to contact developers of the project. Should be in the format <contact_type>:<contact_information>. You should include contact types that will not change over time.
policy: String
Text describing the project's security policy, or a link to it. This should describe what kind of bounties your project offers and the terms under which you offer them.
preferred_languages: Option<Vec<String>>
A list of preferred languages ISO 639-1.
If the field is not None, it MUST contain at least one item.
encryption: Option<String>
A PGP public key block (or similar) or a link to one.
source_code: Option<String>
A URL to the project's source code.
source_release: Option<String>
The release identifier of this build, ideally corresponding to a tag on git that can be rebuilt to reproduce the same binary. 3rd party build verification tools will use this tag to identify a matching GitHub release.
source_revision: Option<String>
The revision identifier of this build, usually a git commit hash that can be rebuilt to reproduce the same binary. 3rd party build verification tools will use this tag to identify a matching GitHub release.
auditors: Option<Vec<String>>
A list of people or entities that audited this smart contract, or links to pages where audit reports are hosted. Note that this field is self-reported by the author of the program and might not be accurate.
acknowledgments: Option<String>
Text containing acknowledgments to security researchers who have previously found vulnerabilities in the project, or a link to it.
additional_information: Option<String>
Text containing any additional information you want to provide, or a link to it.
Required Functions
The following function MUST be implemented to follow the SRC-11 standard.
fn security_information() -> SecurityInformation;
This function takes no input parameters and returns a struct containing contact information for the project owners, information regarding the bug bounty program, other information related to security, and any other information that the developers find relevant.
- This function MUST return accurate and up to date information.
- This function's return values MUST follow the specification for the
SecurityInformationtype. - This function MUST NOT revert under any circumstances.
Rationale
The return structure discussed covers most information that may want to be conveyed regarding the security of the contract, with an additional field to convey any additional information. This should allow easy communication between the project owners and any white hat hackers if necessary.
Backwards Compatibility
This standard does not face any issues with backward compatibility.
Security Considerations
The information is entirely self reported and as such might not be accurate. Accuracy of information cannot be enforced and as such, anyone using this information should be aware of that.
Example ABI
abi SRC11 {
#[storage(read)]
fn security_information() -> SecurityInformation;
}
Example Implementation
Hard coded information
A basic implementation of the security information standard demonstrating how to hardcode information to be returned.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src11-security-information/hardcoded-information/src/main.sw' -->
Variable information
A basic implementation of the security information standard demonstrating how to return variable information that can be edited to keep it up to date. In this example only the contact_information field is variable, but the same method can be applied to any field which you wish to update.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src11-security-information/variable-information/src/main.sw' -->
SRC-12: Contract Factory
The following standard allows for the implementation of a standard ABI for Contract Factories using the Sway Language. The standardized design designates how verification of newly deployed child contracts are handled.
Motivation
A standard interface for Contract Factories provides a safe and effective method of ensuring contracts can verify the validity of another contract as a child of a factory. This is critical on the Fuel Network as contracts cannot deploy other contracts and verification must be done after deployment.
Prior Art
A Contract Factory is a design where a template contract is used and deployed repeatedly with different configurations. These configurations are often minor changes such as pointing to a different asset. All base functionality remains the same.
On Fuel, contracts cannot deploy other contracts. As a result, a Contract Factory on Fuel must register and verify that the bytecode root of a newly deployed child contract matches the expected bytecode root.
When changing something such as a configurable in Sway, the bytecode root is recalculated. The Bytecode Library has been developed to calculate the bytecode root of a contract with different configurables.
Specification
The following functions MUST be implemented to follow the SRC-12; Contract Factory Standard:
Required Functions
fn register_contract(child_contract: ContractId, configurables: Option<Vec<(u64, Vec<u8>)>>) -> Result<b256, str>
The register_contract() function verifies that a newly deployed contract is the child of a contract factory.
- This function MUST verify that the bytecode root of the
child_contractcontract matches the expected bytecode root. - This function MUST calculate the bytecode root IF
configurablesisSome. - This function MUST not revert.
- This function MUST return a
Resultcontaining theb256bytecode root of the newly registered contract or anstrerror message. - This function MAY add arbitrary conditions checking a contract factory child’s validity, such as verifying storage variables or initialized values.
fn is_valid(child_contract: ContractId) -> bool
The is_valid() function returns a boolean representing the state of whether a contract is registered as a valid child of the contract factory.
- This function MUST return
trueif this is a valid and registered child, otherwisefalse.
fn factory_bytecode_root() -> Option<b256>
The factory_bytecode_root() function returns the bytecode root of the default template contract.
- This function MUST return the bytecode root of the template contract.
Optional Functions
The following are functions that may enhance the use of the SRC-12 standard but ARE NOT required.
fn get_contract_id(configurables: Option<Vec<(u64, Vec<u8>)>>) -> Option<ContractId>
The get_contract_id() function returns a registered contract factory child contract with specific implementation details specified by configurables.
This function MUST return Some(ContractId) IF a contract that follows the specified configurables has been registered with the SRC-12 Contract Factory contract, otherwise None.
Rationale
The SRC-12; Contract Factory Standard is designed to standardize the contract factory design implementation interface between all Fuel instances.
Backwards Compatibility
There are no other standards that the SRC-12 requires compatibility.
Security Considerations
This standard takes into consideration child contracts that are deployed with differentiating configurable values, however individual contract behaviours may be dependent on storage variables. As storage variables may change after the contract has been registered with the SRC-12 compliant contract, the standard suggests to check these values upon registration however it is not enforced.
Example ABI
abi SRC12 {
#[storage(read, write)]
fn register_contract(child_contract: ContractId, configurables: Option<Vec<(u64, Vec<u8>)>>) -> Result<b256, str>;
#[storage(read)]
fn is_valid(child_contract: ContractId) -> bool;
#[storage(read)]
fn factory_bytecode_root() -> Option<b256>;
}
abi SRC12_Extension {
#[storage(read)]
fn get_contract_id(configurables: Option<Vec<(u64, Vec<u8>)>>) -> Option<ContractId>;
}
Example Implementation
With Configurables
Example of the SRC-12 implementation where contract deployments contain configurable values that differentiate the bytecode root from other contracts with the same bytecode.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src12-contract-factory/with_configurables/src/with_configurables.sw' -->
Without Configurables
Example of the SRC-12 implementation where all contract deployments are identical and thus have the same bytecode and root.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src12-contract-factory/without_configurables/src/without_configurables.sw' -->
SRC-13: Soulbound Address
The following standard allows for the implementation of Soulbound Address on the Fuel Network. Soulbound Assets are Native Assets sent to the Soulbound Address and cannot be transferred. As Native Assets on the Fuel Network do not require approvals to be spent, any asset sent to an Address may be transferable. The SRC-13 standard provides a predicate interface to lock Native Assets as soulbound.
Motivation
This standard enables soulbound assets on Fuel and allows external applications to query and provide soulbound assets, whether that be decentralized exchanges, wallets, or other external applications.
Prior Art
Native Assets on the Fuel Network do not require the implementation of certain functions such as transfer or approval. This is done directly within the FuelVM and there is no smart contract that requires updating of balances. As such, any assets sent to an Address may be spendable and ownership of that asset may be transferred. For any soulbound assets, spending must be restricted.
Predicates are programs that return a Boolean value and which represent ownership of some resource upon execution to true. All predicates evaluate to an Address based on their bytecode root. A predicate must evaluate to true such that the assets may be spent.
The SRC-13 Soulbound Asset Standard naming pays homage to the ERC-5192: Minimal Soulbound NFTs seen on Ethereum. While there is functionality we may use as a reference, it is noted that Fuel's Native Assets are fundamentally different than Ethereum's tokens.
Specification
Overview
To ensure that some asset shall never be spent, we must apply spending conditions. This can be done with Predicates on Fuel. Any asset sent to a Predicate Address shall never be spent if the predicate never evaluates to true.
We must also ensure every Address on Fuel has its own Predicate. This can be guaranteed by using a configurable where an Address is defined.
Definitions
- Soulbound Address Predicate - The resulting predicate which owns assets on behalf of an
Address. - Soulbound Address - The computed
Addressof the Soulbound Asset Predicate. - Soulbound Asset - Any Native Asset sent to the Soulbound Address.
Soulbound Address Predicate Specification
- The Soulbound Address Predicate SHALL never spend the assets sent to its computed predicate
Addressor Soulbound Address. - The Soulbound Address Predicate SHALL encode an
Addressof which it represents the soulbound address.
Below we define the Soulbound Address Predicate where ADDRESS MUST be replaced with the Address of which the Soulbound Address Predicate represents.
predicate;
configurable {
ADDRESS: Address = Address::from(0x0000000000000000000000000000000000000000000000000000000000000000),
}
fn main() -> bool {
asm (address: ADDRESS) { address: b256 };
false
}
Soulbound Address
The Soulbound Address is the Soulbound Address Predicate's predicate address. A predicate's address(the bytecode root) is defined here.
The Soulbound Address may be computed from the Soulbound Address Predicate's bytecode both on-chain or off-chain. For off-chain computation, please refer to the fuels-rs predicate docs. For on-chain computation, please refer to Sway-Lib's Bytecode Library.
Rationale
On the Fuel Network, the process for sending any Native Assets is the same and does not require any approval. This means that any assets sent to an Address may be spendable and does not require any external spending conditions. In the case of a soulbound asset, we need to ensure the asset cannot be spent.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets and the SRC-20 standard.
Security Considerations
This standard does not introduce any security concerns, as it does not call external contracts, nor does it define any mutations of the contract state.
It should however be noted that any Native Asset on the Fuel Network is not a Soulbound Asset until it is sent to a Soulbound Address.
Example
The following example shows the Soulbound Address Predicate for the 0xe033369a522e3cd2fc19a5a705a7f119938027e8e287c0ec35b784e68dab2be6 Address.
The resulting Soulbound Address is 0x7f28a538d06788a3d98bb72f4b41012d86abc4b0369ee5dedf56cfbaf245d609. Any Native Assets sent to this address will become Soulbound Assets.
predicate;
configurable {
ADDRESS: Address = Address::from(0xe033369a522e3cd2fc19a5a705a7f119938027e8e287c0ec35b784e68dab2be6),
}
fn main() -> bool {
asm (address: ADDRESS) { address: b256 };
false
}
SRC-14: Simple Upgradeable Proxies
The following proposes a standard for simple upgradeable proxies.
Motivation
We seek to standardize a proxy implementation to improve developer experience and enable tooling to automatically deploy or update proxies as needed.
Prior Art
This OpenZeppelin blog post is a good survey of the state of the art at this time.
Proxy designs fall into three essential categories:
- Immutable proxies which are lightweight clones of other contracts but can't change targets
- Upgradeable proxies such as UUPS which store a target in storage and delegate all calls to it
- Diamonds which are both upgradeable and can point to multiple targets on a per method basis
This document falls in the second category. We want to standardize the implementation of simple upgradeable pass-through contracts.
The FuelVM provides an LDC instruction that is used by Sway's std::execution::run_external to provide a similar behavior to EVM's delegatecall and execute instructions from another contract while retaining one's own storage context. This is the intended means of implementation of this standard.
Specification
Required Behavior
The proxy contract MUST maintain the address of its target in its storage at slot 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55 (equivalent to sha256("storage_SRC14_0")).
It SHOULD base other proxy specific storage fields in the SRC14 namespace to avoid collisions with target storage.
It MAY have its storage definition overlap with that of its target if necessary.
The proxy contract MUST delegate any method call not part of its interface to the target contract.
This delegation MUST retain the storage context of the proxy contract.
Required Public Functions
The following functions MUST be implemented by a proxy contract to follow the SRC-14 standard:
fn set_proxy_target(new_target: ContractId);
If a valid call is made to this function it MUST change the target contract of the proxy to new_target.
This method SHOULD implement access controls such that the target can only be changed by a user that possesses the right permissions (typically the proxy owner).
fn proxy_target() -> Option<ContractId>;
This function MUST return the target contract of the proxy as Some. If no proxy is set then None MUST be returned.
Optional Public Functions
The following functions are RECOMMENDED to be implemented by a proxy contract to follow the SRC-14 standard:
fn proxy_owner() -> State;
This function SHALL return the current state of ownership for the proxy contract where the State is either Uninitialized, Initialized, or Revoked. State is defined in the SRC-5; Ownership Standard.
Rationale
This standard is meant to provide simple upgradeability, it is deliberately minimalistic and does not provide the level of functionality of diamonds.
Unlike in UUPS, this standard requires that the upgrade function is part of the proxy and not its target. This prevents irrecoverable updates if a proxy is made to point to another proxy and no longer has access to upgrade logic.
Backwards Compatibility
SRC-14 is intended to be compatible with SRC-5 and other standards of contract functionality.
As it is the first attempt to standardize proxy implementation, we do not consider interoperability with other proxy standards.
Security Considerations
Permissioning proxy target changes is the primary consideration here.
Use of the SRC-5; Ownership Standard is discouraged. If both the target and proxy contracts implement the SRC-5 standard, the owner() function in the target contract is unreachable through the proxy contract. Use of the proxy_owner() function in the proxy contract should be used instead.
Example ABI
abi SRC14 {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId);
#[storage(read)]
fn proxy_target() -> Option<ContractId>;
}
abi SRC14Extension {
#[storage(read)]
fn proxy_owner() -> State;
}
Example Implementation
Minimal Proxy
Example of a minimal SRC-14 implementation with no access control.
contract;
use std::execution::run_external;
use standards::src14::{SRC14, SRC14_TARGET_STORAGE};
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: ContractId = ContractId::zero(),
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
storage::SRC14.target.write(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
storage::SRC14.target.try_read()
}
}
#[fallback]
#[storage(read)]
fn fallback() {
// pass through any other method call to the target
run_external(storage::SRC14.target.read())
}
Owned Proxy
Example of a SRC-14 implementation that also implements proxy_owner().
contract;
use std::execution::run_external;
use standards::src5::{AccessError, State};
use standards::src14::{SRC14, SRC14_TARGET_STORAGE, SRC14Extension};
/// The owner of this contract at deployment.
#[allow(dead_code)]
const INITIAL_OWNER: Identity = Identity::Address(Address::zero());
storage {
SRC14 {
/// The [ContractId] of the target contract.
///
/// # Additional Information
///
/// `target` is stored at sha256("storage_SRC14_0")
target in 0x7bb458adc1d118713319a5baa00a2d049dd64d2916477d2688d76970c898cd55: ContractId = ContractId::zero(),
/// The [State] of the proxy owner.
owner: State = State::Initialized(INITIAL_OWNER),
},
}
impl SRC14 for Contract {
#[storage(read, write)]
fn set_proxy_target(new_target: ContractId) {
only_owner();
storage::SRC14.target.write(new_target);
}
#[storage(read)]
fn proxy_target() -> Option<ContractId> {
storage::SRC14.target.try_read()
}
}
impl SRC14Extension for Contract {
#[storage(read)]
fn proxy_owner() -> State {
storage::SRC14.owner.read()
}
}
#[fallback]
#[storage(read)]
fn fallback() {
// pass through any other method call to the target
run_external(storage::SRC14.target.read())
}
#[storage(read)]
fn only_owner() {
require(
storage::SRC14
.owner
.read() == State::Initialized(msg_sender().unwrap()),
AccessError::NotOwner,
);
}
SRC-15: Off-Chain Native Asset Metadata
The following standard attempts to define arbitrary metadata for any Native Asset that is not required by other contracts onchain, in a stateless manner. Any contract that implements the SRC-15 standard MUST implement the SRC-20 standard.
Motivation
The SRC-15 standard seeks to enable data-rich assets on the Fuel Network while maintaining a stateless solution. All metadata queries are done off-chain using the indexer.
Prior Art
The SRC-7 standard exists prior to the SRC-15 standard and is a stateful solution. The SRC-15 builds off the SRC-7 standard by using the Metadata enum however provides a stateless solution.
The use of generic metadata was originally found in the Sway-Lib's NFT Library which did not use Fuel's Native Assets. This library has since been deprecated.
A previous definition for a metadata standard was written in the original edit of the now defunct SRC-721. This has since been replaced with the SRC-20 standard as SubId was introduced to enable multiple assets to be minted from a single contract.
Specification
Metadata Type
The Metadata enum from the SRC-7 standard is also used to represent the metadata in the SRC-15 standard.
Logging
The following logs MUST be implemented and emitted to follow the SRC-15 standard. Logging MUST be emitted from the contract which minted the asset.
SRC15MetadataEvent
The SRC15MetadataEvent MUST be emitted at least once for each distinct piece of metadata. The latest emitted SRC15MetadataEvent is determined to be the current metadata.
There SHALL be the following fields in the SRC15MetadataEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdfor the metadata.metadata: Themetadatafield SHALL be used for the correspondingMetadatawhich represents the metadata of the asset.
Example:
pub struct SRC15MetadataEvent {
pub asset: AssetId,
pub metadata: Metadata,
}
Rationale
The SRC-15 standard allows for data-rich assets in a stateless manner by associating an asset with some metadata that may later be fetched by the indexer.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets and the SRC-20 standard. This standard is also compatible with the SRC-7 standard which defines a stateful solution. It also maintains compatibility with existing standards in other ecosystems.
Security Considerations
When indexing for SRC-15 metadata, developers should confirm that the contract that emitted the SRC15MetadataEvent is also the contract that minted the asset that the metadata associates with. Additionally, restrictions via access control on who may emit the Metadata should be considered.
Example Implementation
Single Native Asset
Example of the SRC-15 implementation where metadata exists for only a single asset with one SubId.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src15-offchain-metadata/single_asset/src/single_asset.sw' -->
Multi Native Asset
Example of the SRC-15 implementation where metadata exists for multiple assets with differing SubId values.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src15-offchain-metadata/multi_asset/src/multi_asset.sw' -->
SRC-16: Typed Structured Data
The following standard sets out to standardize encoding and hashing of typed structured data. This enables secure off-chain message signing with human-readable data structures.
Motivation
As the Fuel ecosystem expands, there's an increasing need for applications to handle complex, human-readable data structures rather than raw bytes. When users sign messages or transactions, they should be able to clearly understand what they're signing, whether it's a simple asset transfer, or a complex DeFi interaction. Without a standard method for hashing structured data, developers risk implementing their own solutions, which could lead to confusion or compromise security. This standard provides a secure and consistent way to handle encoding and hashing of structured data, ensuring both safety and usability within ecosystem.
This standard aims to:
- Provide a secure, standardized method for hashing structured data
- Enable clear presentation of structured data for user verification during signing
- Support complex data types that mirror Sway structs
- Enable domain separation to prevent cross-protocol replay attacks
- Define a consistent encoding scheme for structured data types
- Remain stateless, not requiring any storage attributes to enable use across all Fuel program types.
Prior Art
This standard uses ideas from Ethereum's EIP-712 standard, adapting its concepts for the Fuel ecosystem. EIP-712 has proven successful in enabling secure structured data signing for applications like the various browser based wallets and signers that are utilized throughout various DeFi protocols.
Specification
Definition of Typed Structured Data 𝕊
The set of structured data 𝕊 consists of all instances of struct types that can be composed from the following types:
Atomic Types:
u8 to u256
bool
b256 (hash)
Dynamic Types:
Bytes // Variable-length byte sequences
String // Variable-length strings
Reference Types:
Arrays (both fixed size and dynamic) Structs (reference to other struct types)
Example struct definition:
struct Mail {
from: Address,
to: Address,
contents: String,
}
Domain Separator Encoding
The domain separator provides context for the signing operation, preventing cross-protocol replay attacks. It is computed as hash_struct(domain) where domain is defined as:
pub struct SRC16Domain {
name: String, // The protocol name (e.g., "MyProtocol")
version: String, // The protocol version (e.g., "1")
chain_id: u64, // The Fuel chain ID
verifying_contract: ContractId, // The contract id that will verify the signature
}
The chain_id field is a u64 that must be encoded by left-padding with zeros and packed in big-endian order to fill a 32-byte value.
The domain separator encoding follows this scheme:
- Add SRC16_DOMAIN_TYPE_HASH
- Add Keccak256 hash of name string
- Add Keccak256 hash of version string
- Add chain ID as 32-byte big-endian
- Add verifying contract id as 32 bytes
Type Encoding
Each struct type is encoded as name ‖ "(" ‖ member₁ ‖ "," ‖ member₂ ‖ "," ‖ … ‖ memberₙ ")" where each member is written as type ‖ " " ‖ name.
Example:
Mail(address from,address to,string contents)
Data Encoding
Definition of hash_struct
The hash_struct function is defined as:
hash_struct(s : 𝕊) = keccak256(type_hash ‖ encode_data(s)) where:
- type_hash = keccak256(encode_type(type of s))
- ‖ represents byte concatenation
- encode_type and encode_data are defined below
Definition of encode_data
The encoding of a struct instance is enc(value₁) ‖ enc(value₂) ‖ … ‖ enc(valueₙ), the concatenation of the encoded member values in the order they appear in the type. Each encoded member value is exactly 32 bytes long.
The values are encoded as follows:
Atomic Values:
- Boolean false and true are encoded as u64 values 0 and 1, padded to 32 bytes
Address,ContractId,Identity, andb256are encoded directly as 32 bytes- Unsigned Integer values (u8 to u256) are encoded as big-endian bytes, padded to 32 bytes
Dynamic Types:
BytesandStringare encoded as their Keccak256 hash
Reference Types:
- Arrays (both fixed and dynamic) are encoded as the Keccak256 hash of their concatenated encodings
- Struct values are encoded recursively as hash_struct(value)
The implementation of TypedDataHash for 𝕊 SHALL utilize the DataEncoder for encoding each element of the struct based on its type.
Final Message Encoding
The encoding of structured data follows this pattern:
encode(domain_separator : 𝔹²⁵⁶, message : 𝕊) = "\x19\x01" ‖ domain_separator ‖ hash_struct(message)
where:
- \x19\x01 is a constant prefix
- ‖ represents byte concatenation
domain_separatoris the 32-byte hash of the domain parametershash_struct(message) is the 32-byte hash of the structured data
Example implementation
const MAIL_TYPE_HASH: b256 = 0x536e54c54e6699204b424f41f6dea846ee38ac369afec3e7c141d2c92c65e67f;
impl TypedDataHash for Mail {
fn type_hash() -> b256 {
MAIL_TYPE_HASH
}
fn struct_hash(self) -> b256 {
let mut encoded = Bytes::new();
encoded.append(
MAIL_TYPE_HASH.to_be_bytes()
);
encoded.append(
DataEncoder::encode_address(self.from).to_be_bytes()
);
encoded.append(
DataEncoder::encode_address(self.to).to_be_bytes()
);
encoded.append(
DataEncoder::encode_string(self.contents).to_be_bytes()
);
keccak256(encoded)
}
}
Rationale
- Domain separators provides protocol-specific context to prevent signature replay across different protocols and chains.
- Type hashes ensure type safety and prevent collisions between different data structures
- The encoding scheme is designed to be deterministic and injective
- The standard maintains compatibility with existing Sway types and practices
Backwards Compatibility
This standard is compatible with existing Sway data structures and can be implemented alongside other Fuel standards. It does not conflict with existing signature verification methods.
Type System Compatibility Notes
When implementing SRC16 in relation to EIP712, the following type mappings and considerations apply:
String Encoding
- Both standards use the same String type and encoding
- SRC16 specifically uses String type only (not Sway's
strorstr[]) - String values are encoded identically in both standards using keccak256 hash
Fixed Bytes
- EIP712's
bytes32maps directly to Sway'sb256 - Encoded using
encode_b256in theDataEncoder - Both standards handle 32-byte values identically
- Smaller fixed byte arrays (
bytes1tobytes31) are not supported in SRC16
Address Types
- EIP712 uses 20-byte Ethereum addresses
- When encoding an EIP712 address, SRC16:
- Takes only rightmost 20 bytes from a 32-byte Fuel Address
- Pads with zeros on the left for EIP712 compatibility
- Example: Fuel
Addressof 32 bytes becomes rightmost 20 bytes in EIP712 encoding
ContractId Handling
ContractIdis unique to Fuel/SRC16 (no equivalent in EIP712)- When encoding for EIP712 compatibility:
- Uses rightmost 20 bytes of
ContractId - Particularly important in domain separators where EIP712 expects a 20-byte address
- Uses rightmost 20 bytes of
Domain Separator Compatibility
// SRC16 Domain (Fuel native)
pub struct SRC16Domain {
name: String, // Same as EIP712
version: String, // Same as EIP712
chain_id: u64, // Fuel chain ID
verifying_contract: ContractId, // Full 32-byte ContractId
}
// EIP712 Domain (Ethereum compatible)
pub struct EIP712Domain {
name: String,
version: String,
chain_id: u256,
verifying_contract: b256, // Only rightmost 20 bytes used
}
Note on verifying_contract field; When implementing EIP712 compatibility within SRC16, the verifying_contract address in the EIP712Domain must be constructed by taking only the rightmost 20 bytes from either a Fuel ContractId. This ensures proper compatibility with Ethereum's 20-byte addressing scheme in the domain separator.
// Example ContractId conversion:
// Fuel ContractId (32 bytes):
// 0x000000000000000000000000a2233d3bf2aa3f0cbbe824eb04afc1acc84c364c
// └─────────────── 20 bytes ───────────────┘
//
// EIP712 Address (20 bytes):
// 0xa2233d3bf2aa3f0cbbe824eb04afc1acc84c364c
// └─────────────── 20 bytes ───────────────┘
Note on EIP712 Domain Separator salt; Within EIP712 the field salt is an optional field to be used at the discretion of the protocol designer. Within SRC16 the EIP712Domain does not use the salt field. The other fields in EIP712Domain are mandatory within SRC16.
Security Considerations
Replay Attacks
Implementations must ensure signatures cannot be replayed across:
Different chains (prevented by chain_id) Different protocols (prevented by domain separator) Different contracts (prevented by verifying_contract)
Type Safety
Implementations must validate all type information and enforce strict encoding rules to prevent type confusion attacks.
Example Implementation
Example of the SRC16 implementation where a contract utilizes the encoding scheme to produce a typed structured data hash of the Mail type.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src16-typed-data/fuel_example/src/main.sw' -->
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src16-typed-data/ethereum_example/src/main.sw' -->
SRC-20: Native Asset
The following standard allows for the implementation of a standard API for Native Assets using the Sway Language. This standard provides basic functionality as well as on-chain metadata for other applications to use.
Motivation
A standard interface for Native Assets on Fuel allows external applications to interact with the native asset, whether that be decentralized exchanges, wallets, or Fuel's Scripts and Predicates.
Prior Art
The SRC-20 Native Asset Standard naming pays homage to the ERC-20 Token Standard seen on Ethereum. While there is functionality we may use as a reference, it is noted that Fuel's Native Assets are fundamentally different than Ethereum's tokens.
There has been a discussion of the Fungible Token Standard on the Fuel Forum. This discussion can be found here.
There has also been a Fungible Token Standard and Non-Fungible Token Standard implementations added to the Sway-Libs repository before the creation of the Sway-Standards repository. The introduction of this standard in the Sway-Standards repository will deprecate the Sway-Libs Fungible Token Standard.
Specification
Required Public Functions
The following functions MUST be implemented to follow the SRC-20 standard:
fn total_assets() -> u64
This function MUST return the total number of individual assets for a contract.
fn total_supply(asset: AssetId) -> Option<u64>
This function MUST return the total supply of coins for an asset. This function MUST return Some for any assets minted by the contract.
fn name(asset: AssetId) -> Option<String>
This function MUST return the name of the asset, such as “Ether”. This function MUST return Some for any assets minted by the contract.
fn symbol(asset: AssetId) -> Option<String>
This function must return the symbol of the asset, such as “ETH”. This function MUST return Some for any assets minted by the contract.
fn decimals(asset: AssetId) -> Option<u8>
This function must return the number of decimals the asset uses - e.g. 8, which means to divide the coin amount by 100000000 to get its user representation. This function MUST return Some for any assets minted by the contract.
Non-Fungible Asset Restrictions
Non-Fungible Tokens (NFT) or Non-Fungible Assets on Fuel are Native Assets and thus follow the same standard as Fungible Native Assets with some restrictions. For a Native Asset on Fuel to be deemed an NFT, the following must be applied:
- Non-Fungible Assets SHALL have a total supply of one per asset.
- Non-Fungible Assets SHALL have a decimal of
0u8.
Logging
The following logs MUST be implemented and emitted to follow the SRC-20 standard.
- IF a value is updated via a function call, a log MUST be emitted.
- IF a value is embedded in a contract as a constant, configurable, or other manner, an event MUST be emitted at least once.
SetNameEvent
The SetNameEvent MUST be emitted when the name of an asset has updated.
There SHALL be the following fields in the SetNameEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdof the asset has been updated.name: Thenamefield SHALL be used for the correspondingOption<String>which represents the name of the asset.sender: Thesenderfield SHALL be used for the correspondingIdentitywhich made the function call that has updated the name of the asset.
Example:
pub struct SetNameEvent {
pub asset: AssetId,
pub name: Option<String>,
pub sender: Identity,
}
SetSymbolEvent
The SetSymbolEvent MUST be emitted when the symbol of an asset has updated.
There SHALL be the following fields in the SetSymbolEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdof the asset has been updated.symbol: Thesymbolfield SHALL be used for the correspondingOption<String>which represents the symbol of the asset.sender: Thesenderfield SHALL be used for the correspondingIdentitywhich made the function call that has updated the symbol of the asset.
Example:
pub struct SetSymbolEvent {
pub asset: AssetId,
pub symbol: Option<String>,
pub sender: Identity,
}
SetDecimalsEvent
The SetDecimalsEvent MUST be emitted when the decimals of an asset has updated.
There SHALL be the following fields in the SetDecimalsEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdof the asset has been updated.decimals: Thedecimalsfield SHALL be used for the correspondingu8which represents the decimals of the asset.sender: Thesenderfield SHALL be used for the correspondingIdentitywhich made the function call that has updated the decimals of the asset.
Example:
pub struct SetDecimalsEvent {
pub asset: AssetId,
pub decimals: u8,
pub sender: Identity,
}
UpdateTotalSupplyEvent
The UpdateTotalSupplyEvent MUST be emitted when the total supply of an asset has updated.
There SHALL be the following fields in the UpdateTotalSupplyEvent struct:
asset: Theassetfield SHALL be used for the correspondingAssetIdof the asset has been updated.supply: Thesupplyfield SHALL be used for the correspondingu64which represents the total supply of the asset.sender: Thesenderfield SHALL be used for the correspondingIdentitywhich made the function call that has updated the total supply of the asset.
Example:
pub struct UpdateTotalSupplyEvent {
pub asset: AssetId,
pub supply: u64,
pub sender: Identity,
}
Rationale
As the SRC-20 Native Asset Standard leverages Native Assets on Fuel, we do not require the implementation of certain functions such as transfer or approval. This is done directly within the FuelVM and there is no smart contract that requires updating of balances. As Fuel is UTXO based, any transfer events may be indexed on transaction receipts.
Following this, we have omitted the inclusion of any transfer functions or events. The provided specification outlines only the functions necessary to implement fully functional native assets on the Fuel Network. Additional functionality and properties may be added as needed.
Backwards Compatibility
This standard is compatible with Fuel's Native Assets. There are no other standards that require compatibility.
Security Considerations
This standard does not introduce any security concerns, as it does not call external contracts, nor does it define any mutations of the contract state.
Example ABI
abi SRC20 {
#[storage(read)]
fn total_assets() -> u64;
#[storage(read)]
fn total_supply(asset: AssetId) -> Option<u64>;
#[storage(read)]
fn name(asset: AssetId) -> Option<String>;
#[storage(read)]
fn symbol(asset: AssetId) -> Option<String>;
#[storage(read)]
fn decimals(asset: AssetId) -> Option<u8>;
}
Example Implementation
Single Native Asset
Example of the SRC-20 implementation where a contract contains a single asset with one SubId. This implementation is recommended for users that intend to deploy a single asset with their contract.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src20-native-asset/multi_asset/src/multi_asset.sw' -->
Multi Native Asset
Example of the SRC-20 implementation where a contract contains multiple assets with differing SubIds. This implementation is recommended for users that intend to deploy multiple assets with their contract.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src20-native-asset/single_asset/src/single_asset.sw' -->
Solidity
A quick Solidity → Sway cross reference for the most commonly used items
- block.timestamp
- msg.sender
- etc
If something is missing here you can most likely find it in the Sway STD Library
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/solidity_cheatsheet/src/main.sw' -->
Hello Sway
The contract keyword at the top defines one of the four program types found in Sway. Others being libraries, scripts and predicates.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/hello_sway/src/main.sw' -->
Variables
Examples of variables in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/variables/src/main.sw' -->
Primitive Types
Examples of primitive data types in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/primitive_types/src/main.sw' -->
Compound Types
Examples of compound data types in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/compound_types/src/main.sw' -->
Blockchain Types
Examples of blockchain data types in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/blockchain_types/src/main.sw' -->
Functions
Examples of functions in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/functions/src/main.sw' -->
Imports
Examples of imports in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/imports/src/main.sw' -->
Project Structures
Internal
└── imports
├── Forc.toml
└── src
├── imports_library.sw
└── main.sw
External
├── imports
│ ├── Forc.toml
│ └── src
│ ├── imports_library.sw
│ └── main.sw
└── math_lib
├── Forc.toml
└── src
├── Q64x64.sw
├── full_math.sw
└── math_lib.sw
All external imports must be defined as dependencies within Forc.toml
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/imports/Forc.toml' -->
Structs
Examples of structs in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/structs/src/main.sw' -->
Tuples
Examples of tuples in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/tuples/src/main.sw' -->
Enums
Examples of enums in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/enums/src/main.sw' -->
Constants
Examples of constants in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/constants/src/main.sw' -->
Configurable Constants
Examples of configurable constants in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/configurable_constants/src/main.sw' -->
Options
Examples of options in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/options/src/main.sw' -->
Results
Examples of if statements in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/results/src/main.sw' -->
If Statements
Examples of if statements in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/control_flow_if/src/main.sw' -->
Match Statements
Examples of match statements in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/control_flow_match_statement/src/main.sw' -->
While Loop
Examples of while loop in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/control_flow_while_loop/src/main.sw' -->
Logging
Examples of logging in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/logging/src/main.sw' -->
Storage Map
Examples of storage map in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/storage_map/src/main.sw' -->
Vector
Examples of vectors in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/vector/src/main.sw' -->
Base Asset
Examples of base asset in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/base_asset/src/main.sw' -->
Library
Example on how to create a library in Sway and how to use it in your Smart Contract. This example also showcases how to use different types of imports in Sway depending on external library or library from the same project.
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/library/src/main.sw' -->
Predicate
Examples of a predicate program type in Sway
| Predicates | Contracts | |
|---|---|---|
| Access data on chain | ❌ | ✅ |
| Read data from smart contracts | ❌ | ✅ |
| Check date or time | ❌ | ✅ |
| Read block hash or number | ❌ | ✅ |
| Read input coins | ✅ | ✅ |
| Read output coins | ✅ | ✅ |
| Read transaction scripts | ✅ | ✅ |
| Read transaction bytecode | ✅ | ✅ |
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/predicate/src/main.sw' -->
Script
Examples of a script program type in Sway
| Predicates | Scripts | |
|---|---|---|
| Access data on chain | ❌ | ✅ |
| Read data from smart contracts | ❌ | ✅ |
| Check date or time | ❌ | ✅ |
| Read block hash or number | ❌ | ✅ |
| Read input coins | ✅ | ✅ |
| Read output coins | ✅ | ✅ |
| Read transaction scripts | ✅ | ✅ |
| Read transaction bytecode | ✅ | ✅ |
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/script/src/main.sw' -->
Big Numbers
Examples of Big Numbers in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/big_number/src/main.sw' -->
SRC20
Examples of a SRC 20 contract in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/src20/src/main.sw' -->
Hashing with Keccak256
Example of how to compute hash in Sway using Keccak256
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/hashing/src/main.sw' -->
Verifying Signatures in Sway
Example of how to verify signatures in Sway
<!-- MDBOOK-INCLUDE-ERROR: File not found '../examples/verify_signature/src/main.sw' -->
Migrations
This section provides information to help with breaking changes, but for full releases, please reference the GitHub release notes tagged in the respective modules.
Sway
Full migration reference can be found here
Rust SDK
Full migration reference can be found here
Typescript SDK
Full migration reference can be found here
Sway Migrations Guide
March 13, 2024
New forc migrate
Below is a simplified example of how to migrate your project quickly. For more information on how to use forc migrate please refer to the forc migrate docs.
Important: Using
forc migraterequires you to use the version of Sway right before the breaking change version.
For example, breaking changes for Sway will come in version v0.67.0, you will need to use v0.66.10 to run forc migrate, in order to migrate properly.
You can compile and migrate using the previous latest version by running the following command:
fuelup component add forc@0.66.10
1. Run forc migrate show
Running forc migrate show will inform you about all the breaking changes in the next release. For example:
Breaking change features:
- storage_domains (https://github.com/FuelLabs/sway/issues/6701)
- partial_eq (https://github.com/FuelLabs/sway/issues/6883)
- try_from_bytes_for_b256 (https://github.com/FuelLabs/sway/issues/6994)
- merge_core_std (https://github.com/FuelLabs/sway/issues/7006)
Migration steps (1 manual, 3 semiautomatic, and 3 automatic):
storage_domains
[M] Review explicitly defined slot keys in storage declarations (`in` keywords)
[S] Explicitly define storage slot keys if they need to be backward compatible
partial_eq
[A] Implement experimental `PartialEq` and `Eq` traits
[S] Remove deprecated `Eq` trait implementations and `experimental_partial_eq` attributes
try_from_bytes_for_b256
[A] Replace `b256::from(<bytes>)` calls with `b256::try_from(<bytes>).unwrap()`
[A] Replace `<bytes>.into()` calls with `<bytes>.try_into().unwrap()`
merge_core_std
[S] Replace `core` with `std` in paths
Experimental feature flags:
- for Forc.toml: experimental = { storage_domains = true, partial_eq = true, try_from_bytes_for_b256 = true, merge_core_std = true }
- for CLI: --experimental storage_domains,partial_eq,try_from_bytes_for_b256,merge_core_std
2. Update forc.toml dependencies
In your Sway project, update the forc.toml file to use the previous latest version of Sway.
// before
[dependencies]
standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.1" }
sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.0" }
// after
[dependencies]
standards = { git = "https://github.com/FuelLabs/sway-standards", tag = "v0.6.3" }
sway_libs = { git = "https://github.com/FuelLabs/sway-libs", tag = "v0.24.2" }
3. Run forc migrate run
Running forc migrate run walks you through each of the breaking changes and helps you apply them to your project.
If you just want to see the breaking changes in your project without migrating them, you can run forc migrate check.
Compiling mira_amm_contract (/mira-v1-core/contracts/mira_amm_contract)
warning: unused manifest key: build-profile.?.release.experimental
Migrating Breaking change feature storage_domains
Checked [storage_domains] Review explicitly defined slot keys in storage declarations (`in` keywords)
Review [storage_domains] Explicitly define storage slot keys if they need to be backward compatible
info: [storage_domains] Explicitly define storage slot keys if they need to be backward compatible
--> /mira-v1-core/contracts/mira_amm_contract/src/main.sw:65:1
|
63 |
64 |
65 | / storage {
66 | | /// Pools storage
... |
79 | | hook: Option<ContractId> = None,
80 | | }
| |_-
|
= help: If the contract owning this storage is behind a proxy, or for any other reason needs
= help: to use previous storage slot keys, those keys must be explicitly assigned to the
= help: storage fields by using the `in` keyword.
= help:
= help: E.g.:
= help: storage {
= help: field in <previous slot key>: u64 = 0,
= help: }
= help:
= help: The previous formula for calculating storage keys was: `sha256("storage.<field name>")`.
= help: The new formula is: `sha256((0u8, "storage.<field name>"))`.
= help:
= help: This migration step will interactively modify the code, based on your input.
= help:
= help: For a detailed migration guide see: https://github.com/FuelLabs/sway/issues/6701
____
The following storage fields will have slot keys calculated by using the new formula:
- storage.pools
- storage.total_pools
- storage.total_reserves
- storage.lp_total_supply
- storage.lp_name
- storage.protocol_fees
- storage.hook
Do you want these fields to have backward compatible storage slot keys, calculated
by using the previous formula?
If yes, this migration step will insert `in` keywords to all of the above fields,
and calculate the storage slot keys by using the previous formula.
1. Yes, assign the backward compatible storage slot keys.
2. No, this contract does not require backwards compatibility.
Enter your choice [1..2]: 1
Changing [storage_domains] Explicitly define storage slot keys if they need to be backward compatible
Source code successfully changed (7 changes).
Finished Project is compatible with the next breaking change version of Sway
4. Switch to the latest version of Sway
// Assuming you have 0.67.0 installed
fuelup default latest
5. Compile your project
forc build
Using the
forc migratetool is highly recommended, and the changes below are only for reference.
Compiler/std-lib: storage collison between variables and StorageMap, allows hidden backdoors, likely loss of funds - #6701
Certain storage types, like, e.g., StorageMap allow storage slots of their contained elements to be defined based on developer's input. E.g., the key in a StorageMap used to calculate the storage slot is a developer input.
To ensure that pre-images of such storage slots can never be the same as a pre-image of compiler generated key of a storage field, we will prefix the pre-images of storage fields with a single byte that denotes the storage field domain. Storage types like StorageMap must have a different domain prefix than this storage field domain which will be set to 0u8.
// before
sha256("storage::<optional namespace 1>::<optional namespace 2>.<field name>")
// after
sha256((0u8, "storage::<optional namespace 1>::<optional namespace 2>.<field name>"))
If the contract owning the storage is behind a proxy, its storage field keys must be backward compatible and the same as the old keys. In this, and any other case where the backward compatibility of the storage slot keys is needed, the old keys must be explicitly defined for storage fields, by using the in keyword and the old keys.
E.g., assume we have a contract with the following storage behind a proxy:
// before
storage {
x: u64 = 0,
namespace_1 {
y: u64 = 0,
namespace_2 {
z: u64 = 0,
}
}
}
// after
storage {
x in 0xc979570128d5f52725e9a343a7f4992d8ed386d7c8cfd25f1c646c51c2ac6b4b: u64 = 0,
namespace_1 {
y in 0x2f055029200cd7fa6751421635c722fcda6ed2261de0f1e0d19d7f257e760589: u64 = 0,
namespace_2 {
z in 0x03d2ee7fb8f3f5e1084e86b02d9d742ede96559e44875c6210c7008e2d184694: u64 = 0,
}
}
}
Replace Eq trait implementations with PartialEq and Eq implementations - #6883
Partial equivalence feature renames the current Eq trait to PartialEq and adds a new, empty Eq trait with PartialEq as a supertrait.
Every existing Eq trait implementation needs to be renamed to PartialEq, and in addition, an empty Eq implementations needs to be added.
// before
impl Eq for SomeType {
fn eq(self, other: Self) -> bool {
self.x == other.x
}
}
// after
impl PartialEq for SomeType {
fn eq(self, other: Self) -> bool {
self.x == other.x
}
}
impl Eq for SomeType {}
If the original implementation implements Eq for a generic type and in addition has Eq on trait constraints, those Eq trait constraints must be replaced by PartialEq in the new PartialEq impl, and remain Eq in the new, empty, Eq impl.
// before
impl<A, B> Eq for (A, B)
where
A: Eq,
B: Eq,
{
fn eq(self, other: Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
// after
impl<A, B> PartialEq for (A, B)
where
A: PartialEq,
B: PartialEq,
{
fn eq(self, other: Self) -> bool {
self.0 == other.0 && self.1 == other.1
}
}
impl<A, B> Eq for (A, B)
where
A: Eq,
B: Eq,
{}
Implement TryFrom<Bytes> for b256 - #6994
Replace calls to from(bytes)/bytes.into() with try_from(bytes)/bytes.try_into().
Calls to from:
// before
let result = b256::from(some_bytes);
// after
let result = b256::try_from(some_bytes).unwrap();
Calls to into:
// before
let result = some_bytes.into();
// after
let result = some_bytes.try_into().unwrap();
Merge core and std libraries - #7006
Currently, we have two standard libraries, core and std. The distinction between them is rather arbitrary, and we want to merge them into a single library called std. All the existing modules in the core library will be moved to the std library, but their content will not be changed.
// before
use core::ops::*;
impl core::ops::Eq for SomeType {
fn eq(self, other: Self) -> bool {
self.x == other.x
}
}
let _ = core::codec::encode(0u64);
// after
use std::ops::*;
impl std::ops::Eq for SomeType {
fn eq(self, other: Self) -> bool {
self.x == other.x
}
}
let _ = std::codec::encode(0u64);
August 16, 2024
#[namespace()] attribute is no longer supported - #6279
We no longer support the #[namespace()] attribute. If you use it, notably with SRC14, you should migrate to using the in keyword if you want backwards compatibility. If you just care about namespacing, you should use the new namespacing syntax.
Backwards compatibility places foo at sha256("storage_example_namespace_0")
// before
#[namespace(example_namespace)]
storage {
foo: u64 = 0,
}
// after
storage {
foo in 0x1102bf23d7c2114d6b409df4a1f8f7982eda775e800267be65c1cc2a93cb6c5c: u64 = 0,
}
New / recommended method places foo at sha256("storage::example_namespace.foo")
// new
storage {
example_namespace {
foo: u64 = 0,
},
}
Configurables are no longer allowed in pattern matching and shadowing - #6289
The code below does not compile any more.
configurable {
X: u8 = 10,
}
fn main() {
let X = 101u8; // ERROR: Variable "X" shadows configurable of the same name.
}
configurable {
X: u8 = 10,
}
fn main() {
match var {
(0, X) => true // ERROR: "X" is a configurable and configurables cannot be matched against.
}
}
New ABI specification format - #6254
The new ABI specification format is hash based to improve support for indexing. There were also updates to support the latest VM features.
Added variable length message support when verifying ed signatures - #6419
ed_verify was changed to use Bytes for the message instead of b256 for a message hash.
// before
pub fn ed_verify(public_key: b256, signature: B512, msg_hash: b256)
// after
pub fn ed_verify(public_key: b256, signature: B512, msg: Bytes)
Some STD functions now return an Option instead of reverting - #6405, #6414, #6418
Some functions in the STD now return an Option instead of reverting. This allows developers to fail gracefully. More functions will do this in the future.
// before
let my_predicate_address: Address = predicate_address();
// after
let my_predicate_address: Address = predicate_address().unwrap();
Some STD functions now return types have been updated to match the Fuel Specifications
output_count()now returns au16over au64
Before:
let output_count: u64 = output_count();
After:
let my_output_count: u16 = output_count();
tx_maturitynow returns anOption<u32>over anOption<u64>
Before:
let my_tx_maturity: u64 = tx_maturity().unwrap()
After:
let my_tx_maturity: u32 = tx_maturity().unwrap()
Some STD functions have been made private. These will no longer be available for developers to use
input_pointer()output_pointer()tx_witness_pointer()tx_script_start_pointer()tx_script_data_start_pointer()
The following functions now follow this format:
Inputs:
input_type()input_predicate_data()input_predicate()input_message_sender()input_message_recipient()input_message_data_length()input_message_data()input_message_nonce()
Outputs:
output_type()output_amount()
Transactions:
tx_script_length()tx_script_data_length()tx_witness_data_length()tx_witness_data()tx_script_data()tx_script_bytecode()tx_script_bytecode_hash()
Non-breaking Changes
New partial support for slices.
Automated proxy creation and deployment with forc.
Rust SDK Migrations Guide
March 17, 2025
Bump minimum fuel-core-* versions - #1600
Minimum fuel-core-* versions bumped to 0.41.7
#![allow(unused)] fn main() { // before fuel-core = { version = "0.41.3", default-features = false, features = [ "wasm-executor", ] } fuel-core-chain-config = { version = "0.41.3", default-features = false } ... }
#![allow(unused)] fn main() { // after fuel-core = { version = "0.41.7", default-features = false, features = [ "wasm-executor", ] } fuel-core-chain-config = { version = "0.41.7", default-features = false } ... }
Wallet refactoring - #1620
ImpersonatedAccount is removed
To achieve the same functionality instantiate a `FakeSigner:
#![allow(unused)] fn main() { // before let address = Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1") .unwrap(); let address = Bech32Address::from(address); let impersonator = ImpersonatedAccount::new(address, Some(provider.clone())); }
#![allow(unused)] fn main() { // after let some_address = wallet.address().clone(); let fake_signer = FakeSigner::new(some_address); let impersonator = Wallet::new(fake_signer, provider.clone()); }
AwsKmsSigner and GoogleKmsSigner moved
under fuels::accounts::signers::kms::aws and fuels::accounts::signers::kms::google, respectfully.
#![allow(unused)] fn main() { // before use fuels::accounts::kms::AwsKmsSigner; use fuels::accounts::kms::GoogleKmsSigner; }
#![allow(unused)] fn main() { // after use fuels::accounts::signers::kms::aws::AwsKmsSigner; use fuels::accounts::signers::kms::google::GoogleKmsSigner; }
KmsWallet removed
use an ordinary Wallet now with a kms signer (aws or google)
WalletUnlocked and Wallet substituted by Wallet<Unlocked<S = PrivateKeySigner>> or Wallet<Locked>
#![allow(unused)] fn main() { // before wallet.set_provider(provider.clone()); ... let mut wallet = WalletUnlocked::new_random(None); let coins: Vec<Coin> = setup_single_asset_coins( wallet.address(), Default::default(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); let chain_config = ChainConfig { consensus_parameters: consensus_parameters.clone(), ..ChainConfig::default() }; let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?; wallet.set_provider(provider.clone()); assert_eq!(consensus_parameters, provider.consensus_parameters().await?); ... let wallet = WalletUnlocked::new_random(None); }
#![allow(unused)] fn main() { // after let wallet = Wallet::new(signer, provider.clone()); ... let mut rng = thread_rng(); let signer = PrivateKeySigner::random(&mut rng); let coins: Vec<Coin> = setup_single_asset_coins( signer.address(), Default::default(), DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT, ); let chain_config = ChainConfig { consensus_parameters: consensus_parameters.clone(), ..ChainConfig::default() }; let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?; let wallet = Wallet::new(signer, provider.clone()); assert_eq!(consensus_parameters, provider.consensus_parameters().await?); ... let wallet = launch_provider_and_get_wallet().await?; }
The provider is now mandatory for Wallet::new.
Common operations in the new API:
Creating a random wallet
a) Two step (useful when you haven't started the node but need the address)
#![allow(unused)] fn main() { // Create a random private key signer let signer = PrivateKeySigner::random(&mut rng); let coins = setup_single_asset_coins(signer.address(), asset_id, 1, DEFAULT_COIN_AMOUNT); let provider = setup_test_provider(coins.clone(), vec![], None, None).await?; let wallet = Wallet::new(signer, provider); }
b) One step (when you already have a provider)
#![allow(unused)] fn main() { let wallet = Wallet::random(&mut rng, provider.clone()); }
Locking a wallet
#![allow(unused)] fn main() { let locked_wallet = wallet.lock(); }
Creating a locked wallet
#![allow(unused)] fn main() { let wallet = Wallet::new_locked(addr, provider.clone()); }
Wallets no longer sign
You use one of the signers for that. Or, if your wallet is unlocked, get its signer by calling wallet.signer().
ViewOnlyAccount no longer requires core::fmt::Debug and core::clone::Clone as supertraits
Wallet no longer handles encrypting keys for disk storage
Use the fuels::accounts::Keystore for that (feature-gated under accounts-keystore)
AWS/Google kms feature flags changed
They're now accounts-signer-aws-kms and accounts-signer-google-kms.
Use total_gas and total_fee from tx status - #1574
- Removed
get_response_frommethod fromCallHandlers CallResponserefactored and addedtx_status: Successfield- Method
get_responseacceptsTxStatusinstead ofVec<Receipts> - Method
newis removed formCallResponse GasValidationtrait is removed from transaction buildersAccountstransfermethod returnsResult<TxResponse>Accountsforce_transfer_to_contractmethod returnsResult<TxResponse>Accountswithdraw_to_base_layermethod returnsResult<WithdrawToBaseResponse>Executable<Loader>'supload_blobreturnsResult<Option<TxResponse>>- Contract's
deployanddeploy_if_not_existsreturnResult<DeployResponse>andResponse<Option<DeployResponse>>respectively TransactionCost's fieldgas_usedrenamed toscript_gas
August 16, 2024
Unfunded read only calls - #1412
SizedAsciiString no longer implements AsRef<[u8]>. To get the underlying bytes you can turn it into a &str via the new AsRef<str> and call as_bytes() on the &str: `sized_string.as_ref().as_bytes()``
#![allow(unused)] fn main() { // before let bytes: &[u8] = sized_str.as_ref(); }
#![allow(unused)] fn main() { // after let bytes: &[u8] = sized_str.as_ref().as_bytes(); }
build_without_signatures is now achieved by setting the build strategy to BuildStrategy::NoSignatures on the transaction builder before calling build
#![allow(unused)] fn main() { // before let mut tx = tb.build_without_signatures(provider).await?; }
#![allow(unused)] fn main() { // after let mut tx = tb.with_build_strategy(ScriptBuildStrategy::NoSignatures).build(provider).await?; }
.simulate() now accepts an Execution argument allowing for Realistic or StateReadOnly simulations.
#![allow(unused)] fn main() { // before let stored = contract_methods.read().simulate().await?; }
#![allow(unused)] fn main() { // after let stored = contract_methods.read().simulate(Execution::StateReadOnly).await?; }
Accounts now cover max fee increase due to tolerance - #1464
fee_checked_from_tx is removed from all transaction builders. max fee can now be estimated using the new method estimate_max_fee which takes into account the max fee estimation tolerance set on the builders.
#![allow(unused)] fn main() { // before let transaction_fee = tb.fee_checked_from_tx(provider) .await? .ok_or(error_transaction!( Other, "error calculating `TransactionFee`" ))?; let total_used = transaction_fee.max_fee() + reserved_base_amount; }
#![allow(unused)] fn main() { // after let max_fee = tb.estimate_max_fee(provider).await?; let total_used = max_fee + reserved_base_amount; }
Account impersonation - #1473
The SDK previously performed transaction validity checks, including signature verification, before sending a transaction to the network. This was problematic since the checks also included signature verification even when utxo validation was turned off. To enable this feature and prevent future issues like failed validation checks due to version mismatches between the network and the SDK's upstream dependencies, we decided to remove the check. Since the SDK already abstracts building transactions for common cases (contract calls, transfers, etc.), validity issues are unlikely. If needed, we can still expose the validity checks as part of the transaction builder or our transaction structs.
#![allow(unused)] fn main() { /* A `ImpersonatedAccount` simulates ownership of assets held by an account with a given address. `ImpersonatedAccount` will only succeed in unlocking assets if the the network is setup with utxo_validation set to false. */ let node_config = NodeConfig { utxo_validation: false, ..Default::default() }; }
Deploying large contracts (loader + blob support) - #1472
Contract::new is removed, replaced with Contract::regular with three states
First: A regular contract
What you're used to seeing. It is either initialized from raw code or loaded from a file:
#![allow(unused)] fn main() { let contract = Contract::regular(contract_binary, Salt::zeroed(), vec![]); }
or
#![allow(unused)] fn main() { let contract = Contract::load_from( "sway/contracts/storage/out/release/storage.bin", LoadConfiguration::default(), )?; }
With the notable addition of being able to set configurables (previously possible only when using load_from):
#![allow(unused)] fn main() { let contract = Contract::regular(binary, Salt::zeroed(), vec![]).with_configurables(configurables); }
a regular contract can be deployed via deploy, which hasn't changed, or via smart_deploy that will use blobs/loader if the contract is above what can be deployed in a create tx:
#![allow(unused)] fn main() { let contract_id = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob) .await?; }
Second: Loader contract, blobs pending upload
You can turn a regular contract into a loader contract:
#![allow(unused)] fn main() { let contract = Contract::load_from( contract_binary, LoadConfiguration::default(), )? .convert_to_loader(max_words_per_blob)? }
or, if you have the blobs, create it directly:
#![allow(unused)] fn main() { let contract = Contract::loader_for_blobs(blobs, random_salt(), vec![])?; }
You can also revert back to the regular contract via revert_to_regular.
If you now call deploy the contract will first deploy the blobs and then the loader itself.
You can also split this into two parts by first calling upload_blobs and then deploy:
#![allow(unused)] fn main() { let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())? .convert_to_loader(1024)? .upload_blobs(&wallet, TxPolicies::default()) .await? .deploy(&wallet, TxPolicies::default()) .await?; }
doing so will have deploy only submit the create tx while the uploading will be done in upload_blobs.
Third: Loader, with blobs deployed
You arrive at this contract type by either having the blob ids and creating it manually:
#![allow(unused)] fn main() { let contract = Contract::loader_for_blob_ids(all_blob_ids, random_salt(), vec![])?; }
or by calling upload_blobs as in the previous case:
#![allow(unused)] fn main() { let contract = Contract::load_from( contract_binary, LoadConfiguration::default().with_salt(random_salt()), )? .convert_to_loader(max_words_per_blob)? .upload_blobs(&wallet, TxPolicies::default()) .await?; }
Calling deploy on this contract only deploys the loader.
TypeScript SDK Migrations Guide
March 17, 2025
Made ResourceCache consider resource owner - #3697
//before
provider.cache?.getActiveData();
provider.cache?.isCached(key);
//after
const owner = wallet.address.toB256();
provider.cache?.getActiveData(owner)
provider.cache?.isCached(owner, key);
Upgrade fuel-core to 0.41.7 - #3590
Because of the latest fuel-core changes, TS SDK does not throw the following error codes and messages anymore:
1. NOT_ENOUGH_FUNDS
// before
"The account(s) sending the transaction don't have enough funds to cover the transaction."
// after
"Insufficient funds or too many small value coins. Consider combining UTXOs."
2. MAX_COINS_REACHED
// before
"The account retrieving coins has exceeded the maximum number of coins per asset. Please consider combining your coins into a single UTXO."
// after
"Insufficient funds or too many small value coins. Consider combining UTXOs."
Both error codes were removed in favor of INSUFFICIENT_FUNDS_OR_MAX_COINS
February 4, 2025
Remove pageInfo from getBalances GraphQL operations - #3652
- The
pageInfofield has been removed from the response of theprovider.operations.getBalancesquery.
// before
const { balances, pageInfo } = await provider.operations.getBalances({
first: 100,
filter: { owner: wallet.address.toB256() },
});
// after
const { balances } = await provider.operations.getBalances({
first: 100,
filter: { owner: wallet.address.toB256() },
});
The getBalances method of the Provider class remains unchanged, as it never returned pageInfo:
// not affected
const { balances } = await provider.getBalances();
Remove ContractUtils namespaced export - #3570
ContractUtilswas removed and the underlying functionsgetContractRoot(),getContractStorageRoot(),getContractId(),hexlifyWithPrefix()are now exported directly fromfuels.
// before
import { ContractUtils } from 'fuels';
// after
import { getContractRoot, getContractStorageRoot, getContractId, hexlifyWithPrefix } from 'fuels';
January 10, 2025
Making provider initialization sync again - #3514
1. Provider Instantiation
- Going from
asynctosync
// before
const provider = await Provider.create(NETWORK_URL);
// after
const provider = new Provider(NETWORK_URL);
2. Provider methods
- The following methods are now
async
// before
provider.getNode();
provider.getChain();
provider.getChainId();
provider.getBaseAssetId();
provider.getGasConfig();
provider.validateTransaction();
// after
await provider.getNode();
await provider.getChain();
await provider.getChainId();
await provider.getBaseAssetId();
await provider.getGasConfig();
await provider.validateTransaction();
3. TransferParams and ContractTransferParams
- Property
assetIdis now required byTransferParamsandContractTransferParams
export type TransferParams = {
destination: string | AbstractAddress;
amount: BigNumberish;
- assetId?: BytesLike;
+ assetId: BytesLike;
};
export type ContractTransferParams = {
contractId: string | AbstractAddress;
amount: BigNumberish;
- assetId?: BytesLike;
+ assetId: BytesLike;
};
4. Transaction Response
- The constructor now requires a
chainId
// before
new TransactionResponse('0x..', provider);
// after
new TransactionResponse('0x..', provider, chainId);
autoCost for transaction estimation and funding - #3539
To be brought inline with autoCost, funding a contract and script call has been migrated from fundWithRequiredCoins to autoCost:
// before
const request: ScriptTransactionRequest = contract.functions.add(1).fundWithRequiredCoins();
// after
const request: ScriptTransactionRequest = contract.functions.add(1).autoCost();
Remove redundant gas price call for tx summary - #3559
calculateTXFeeForSummaryand subsequently theCalculateTXFeeForSummaryParamsno longer accept atotalFeeproperty. If you have thetotalFee, then there is no need to call thecalculateTxFeeForSummary()function.
// before
const totalFee = bn(..):
calculateTXFeeForSummary({ ..., totalFee } as CalculateTXFeeForSummaryParams);
// after
calculateTXFeeForSummary({ ... } as CalculateTXFeeForSummaryParams);
Prevent implicit asset burn - #3540
// before
const transactionRequest = new ScriptTransactionRequest();
transactionRequest.inputs.push({ ... });
// since outputs weren't added, assets would be burned
await sender.sendTransaction(transactionRequest);
// after
const transactionRequest = new ScriptTransactionRequest();
transactionRequest.inputs.push({ ... });
// now, an error will be thrown unless `enableAssetBurn`is true,
// in which case, assets can still be burned
await sender.sendTransaction(transactionRequest, {
enableAssetBurn: true,
});
Remove unused operations - #3553
The following operations have been removed from the OperationName enum, as they were never used to assemble operations:
OperationName.mintOperationName.predicatecallOperationName.scriptOperationName.sent
Remove receipts deprecated properties - #3552
All receipts deprecated properties were removed:
// before
ReceiptCall.from
ReceiptLog.val0
ReceiptLog.val1
ReceiptLog.val2
ReceiptLog.val3
ReceiptLogData.val0
ReceiptLogData.val1
ReceiptTransfer.from
ReceiptTransferOut.from
// after
ReceiptCall.id
ReceiptLog.ra
ReceiptLog.rb
ReceiptLog.rc
ReceiptLog.rd
ReceiptLogData.ra
ReceiptLogData.rb
ReceiptTransfer.id
ReceiptTransferOut.id
Remove receipt coders - #3551
All previously deprecated receipt coders have been removed. These classes were barely used aside from a few internal helpers, which were converted to utility functions.
// before
const messageId = ReceiptMessageOutCoder.getMessageId({
sender,
recipient,
nonce,
amount,
data,
});
const assetId = ReceiptMintCoder.getAssetId(contractId, subId);
const assetId = ReceiptBurnCoder.getAssetId(contractId, subId);
// after
import { getMessageId, getAssetId } from 'fuels'
const messageId = getMessageId({
sender,
recipient,
nonce,
amount,
data,
});
const assetId = getAssetId(contractId, subId);
Remove deprecated submitAndAwait operation - #3548
submitAndAwaitoperation was removed
After being deprecated since #3101, we have removed this operation altogether. Please use the submitAndAwaitStatus method instead which gives the same results as submitAndAwait. If you are interested in the deprecation/removal reasons, please refer to https://github.com/FuelLabs/fuel-core/issues/2108.
// before
const response = await provider.operations.submitAndAwait(txRequest);
// after
const response = await provider.operations.submitAndAwaitStatus(txRequest);
Remove Bech32 address - #3493
- We no longer support Bech32 addresses
// before
import { Address, Bech32Address } from "fuels";
const bech32Address: Bech32Address = "fuel1234";
const address = new Address(bech32Address);
// after
import { Address, B256Address } from "fuels";
const b256Address: B256Address = "0x1234";
const address = new Address(b256Address);
-
Removed
INVALID_BECH32_ADDRESSerror code. -
Removed associated Bech32 helper functions.
normalizeBech32isBech32toB256getBytesFromBech32toBech32clearFirst12BytesFromB256
Redistributed the @fuel-ts/interfaces package - #3492
- Removed the
AbstractAddressclass; use theAddressclass instead.
// before
import { AbstractAddress } from 'fuels';
// after
import { Address } from 'fuels';
- Removed the
@fuel-ts/interfacespackage; use thefuelspackage instead.
// before
import { BytesLike } from '@fuel-ts/interfaces'
// after
import { BytesLike } from 'fuels'
Optimizing frontend apps - #3573
ScriptTransactionRequest.autoCost()has been renamed toScriptTransactionRequest.estimateAndFund(), initially introduced by #3535
// before
await request.autoCost(wallet);
// after
await request.estimateAndFund(wallet);
BaseInvocationScope.autoCost()has been renamed back toBaseInvocationScope.fundWithRequiredCoins(), initially introduced by #3535
// before
const request = await contract.functions.increment().autoCost();
// after
const request = await contract.functions.increment().fundWithRequiredCoins();
November 15, 2024
onDeploy fuels config supports all Sway program types - #3383
- Changed the outputted data from the
onDeploycallback method for thefuels.config.ts. Instead of just emitting the deployed contracts (as an array), it will now emit an object withcontracts,predicatesandscripts.
// Before (fuels.config.ts)
import { createConfig, FuelsConfig, DeployedContract } from 'fuels';
export default createConfig({
output: 'dir/out',
onDeploy: (config: FuelsConfig, deployedContracts: DeployedContract[]) => {
console.log('contracts', deployedContracts);
}
});
// After (fuels.config.ts)
import { createConfig, FuelsConfig, DeployedData } from 'fuels';
export default createConfig({
output: 'dir/out',
onDeploy: (config: FuelsConfig, deployed: DeployedData[]) => {
console.log('contracts', deployed.contracts);
console.log('predicates', deployed.predicates);
console.log('scripts', deployed.scripts);
}
});
Remove unnecessary nonce from message gql queries - #3298
- Removed the
nonceproperty fromProvider.operations.getMessageByNonce(). This can still be retrieved byProvider.getMessageByNonce().
Refactor predicate and script deployment - #3389
ContractFactory.deployAsBlobTxForScript has been removed in favor of Predicate.deploy and Script.deploy:
// before
const factory = new ContractFactory(scriptBytecode, scriptAbi, wallet);
const { waitForResult } = await factory.deployAsBlobTxForScript();
const { loaderBytecode, configurableOffsetDiff } = await waitForResult();
// after
const script = new Script(scriptBytecode, scriptAbi, wallet);
const { blobId, waitForResult } = await script.deploy(deployerWallet);
const loaderScript = await waitForResult();
const predicate = new Predicate({ bytecode, abi, provider });
const { blobId, waitForResult } = await predicate.deploy(deployerWallet);
const loaderPredicate = await waitForResult();
Mandate abi in Predicate constructor - #3387
- Instantiating a
Predicatenow requires providing itsabi. If you want to use thePredicateas anAccount, please instantiate it via theAccountclass
// before
const predicate = new Predicate({ provider, bytecode }); // worked even though abi is missing
// after
const predicate = new Predicate({ abi, provider, bytecode }); // abi is now mandatory
// predicate as account
const account = new Account(predicateAddress, provider);
Optimize getTransactions query - #3336
- The response format for
Provider.getTransactionsremains the same. However, the response format for the queryProvider.operations.getTransactionshas been modified.
// before
query getTransactions {
id
rawPayload
status {
...
}
}
// after
query getTransactions {
rawPayload
}
Limit TX pagination number for getTransactionsSummaries - #3400
- The pagination number for
getTransactionsSummariesis limited to60now
// before
const { transactions } = await getTransactionsSummaries({
provider,
filters: {
owner: account.address.toB256(),
first: 200,
},
});
// after
const { transactions } = await getTransactionsSummaries({
provider,
filters: {
owner: account.address.toB256(),
first: 60, // Limit is 60 now. A higher value will result in an error
},
});
Remove blockId in transaction list responses - #3379
- The
blockIdproperty has been removed from the following GraphQL queries used to list past transactions:
const { transactions } = await getTransactionsSummaries({ ... });
const { transactionsByOwner } = await provider.operations.getTransactionsByOwner({ ... });
If the blockId is required for a given transaction, it needs to be queried separately with getTransactionSummary helper:
import { getTransactionSummary } from 'fuels';
const transaction = await getTransactionSummary({
id,
provider,
});
Note: The blockId is still available in the result for a submitted transaction.
Optimize coin gql queries - #3301
-
The
Provider.operations.getCoins()andProvider.operations.getCoinsToSpendfunction no longer return the owner. These methods shouldn't be called directly but are used internally to formulate responses from the SDK. -
Removed the property
ownerfrom theProvider.operations.getCoinsToSpend()function. Suggest to use the owner from the input parameters.
October 13, 2024
Checksum method to remove 0x before hashing - #3313
We fixed the checksum utilities:
Address.toChecksum()Address.isChecksumValid()
Now, we correctly remove the leading 0x before hashing the address.
Because of this, previous values were invalid, and the update is required.
October 10, 2024
Bump transaction pagination limit to 60 - #3306
- A limit was added of 60 transactions to the
provider.getTransactions()method.
Made Address toString and valueOf returns checksum - #3310
The return of both Address.toString() and Address.valueOf was modified to return the address checksum instead of the Bech32 string
// before
const address = new Address('fuel1elnmzsav56dqnp95sx4e2pckq36cvae9ser44m5zlvgtwxw49fmqd7e42e');
address.toString()
// fuel1elnmzsav56dqnp95sx4e2pckq36cvae9ser44m5zlvgtwxw49fmqd7e42e
address.valueOf()
// fuel1elnmzsav56dqnp95sx4e2pckq36cvae9ser44m5zlvgtwxw49fmqd7e42e
// after
const address = new Address('fuel1elnmzsav56dqnp95sx4e2pckq36cvae9ser44m5zlvgtwxw49fmqd7e42e');
address.toString()
// 0xEf86aFa9696Cf0dc6385e2C407A6e159A1103cEfB7E2Ae0636FB33d3cb2A9E4A
address.valueOf()
// 0xEf86aFa9696Cf0dc6385e2C407A6e159A1103cEfB7E2Ae0636FB33d3cb2A9E4A
Slim down chainInfoFragment and GasCostsFragment - #3286
latestBlockis no longer part of theChainInforeturn ofprovider.getChain(). You can fetch it viaprovider.getBlock('latest').ChainInfo['consensusParameters']['gasCosts']has been slimmed down to only contain data necessary for the operation of the SDK. Up until now, the SDK was fetching more than it needed. If this change affects you, you will have to create a custom graphql query forgasCostsfor the additional data you need.
Optimize balance queries - #3296
- Removed the
ownerandassetIdproperties from the response ofProvider.operations.getBalance(). These properties are also required arguments to execute the function so are redundant in the response. Should you require these values, you should take them from the values that you passed to the function. - Removed the
ownerproperty from the response ofProvider.operations.getBalances(). This property is a required argument to execute the function so is redundant in the response. Should you require this value, you should take it from the value that you passed to the function.
August 30, 2024
Consider message on resources cache - #2872
The provider option flag cacheUtxo was renamed to resourceCacheTTL
// before
const provider = await Provider.create(FUEL_NETWORK_URL, {
cacheUtxo: 5000,
});
using launched = await launchTestNode({
providerOptions: {
cacheUtxo: 5000,
},
});
// after
const provider = await Provider.create(FUEL_NETWORK_URL, {
resourceCacheTTL: 5000,
});
using launched = await launchTestNode({
providerOptions: {
resourceCacheTTL: 5000,
},
});
Prettify typegen api - #2824
Predicate class
Predicateclass constructor parameters renamed:inputData>data
// before
import { Predicate } from 'fuels';
const predicate = new Predicate({
...unchangedParameters,
inputData,
});
// after
import { Predicate } from 'fuels';
const predicate = new Predicate({
...unchangedParameters,
data,
});
- Typegen extended/generated
Predicatenow accepts a single parameter for initialization
// before
import { TestPredicateAbi__factory } from './typegend';
TestPredicateAbi__factory.createInstance(provider, data, configurableConstants);
// after
import { TestPredicate } from './typegen';
new TestPredicate({
provider,
data,
configurableConstants
});
launchTestNode utility
- Renamed
contractsConfigs[].deployertocontractsConfigs[].factory - Removed
contractsConfigs[].bytecodeand.hex.tsfile
The bytecode is now saved within the factory class, so you don't have to deal with it.
// before
import { TokenAbi__factory } from './typegend';
import TokenAbiHex from './typegend/contracts/TokenAbi.hex';
using launched = await launchTestNode({
contractsConfigs: [{
deployer: TokenAbi__factory,
bytecode: TokenAbiHex
}],
});
// after
import { TokenFactory } from './typegend';
using launched = await launchTestNode({
contractsConfigs: [{
factory: TokenFactory,
}],
})
Renamed method deployContract to deploy
Removed the redundant suffix on the ContractFactory class method name.
// before
import { ContractFactory } from 'fuels';
const factory = new ContractFactory(wallet);
factory.deployContract();
// after
import { ContractFactory } from 'fuels';
const factory = new ContractFactory(wallet);
factory.deploy();
Typegen Contract template
- Removed
Abi__factorysuffix from class names - The file
<name>.hexwas removed (access it via<Name>.bytecode) - The files
<name>__factory.tsand<name>.d.dtswere merged into<name>.ts - The class
<Name>and the interface<Name>Abiare now just<Name> - Method
<Name>Factory.deployContract()renamed to<Name>Factory.deploy() - You may need to remove the previously generated
<typegenDir>/contracts/factoriesdirectory
// before
import { TestContractAbi, TestContract__factory } from './typegen'
import testContractBytecode from './typegen/contracts/TestContract.hex'
const instance = await TestContract__factory.connect(id, wallet);
const deploy = await TestContract__factory.deployContract(testContractBytecode, wallet);
const { contract } = await deploy.waitForResult();
// after
import { TestContract, TestContractFactory } from './typegen'
const instance = new TestContract(id, wallet);
const deploy = await TestContractFactory.deploy(wallet);
const { contract } = await deploy.waitForResult();
Typegen Predicate template
- Removed
Abi__factorysuffix from class names - Started accepting a single parameter object in constructor
- You may need to remove the previously generated
<typegenDir>/predicates/factoriesdirectory
// before
import { TestPredicateAbi__factory } from './typegen'
const predicate = TestPredicateAbi__factory.createInstance(provider);
// after
import { TestPredicate } from './typegen'
const predicate = new TestPredicate({ provider });
Typegen Script template
- Removed
Abi__factorysuffix from class names - You may need to remove the previously generated
<typegenDir>/scripts/factoriesdirectory
// before
import { TestPredicateAbi__factory } from './typegen'
const script = TestScriptAbi__factory.createInstance(wallet);
// after
import { TestPredicate } from './typegen'
const script = new TestScript(wallet);
Non-blocking blob deployment - #2929
The transaction ID from a contract deployment is now returned as a promise.
// before
import { ContractFactory } from 'fuels';
const factory = new ContractFactory(bytecode, abi, wallet);
const { waitForResult, contractId, transactionId } = await factory.deploy();
console.log(transactionId); // 0x123....
// after
import { ContractFactory } from 'fuels';
const factory = new ContractFactory(bytecode, abi, wallet);
const { waitForResult, contractId, waitForTransactionId } = await factory.deploy();
const transactionId = await waitForTransactionId();
console.log(transactionId); // 0x123....
Improve () and Option<T> type handling - #2777
()andOption<T>Sway types are now either required or optional, dependent on where the argument appears in the function arguments.
Given these Sway functions:
fn type_then_void_then_type(x: u8, y: (), z: u8) -> ()
fn type_then_void_then_void(x: u8, y: (), z: ()) -> ()
fn type_then_option_then_type(x: u8, y: Option<u8>, z: u8) -> ()
fn type_then_option_then_option(x: u8, y: Option<u8>, z: Option<u8>) -> ()
This is what changes:
// before
contract.functions.type_then_void_then_type(42, 43)
contract.functions.type_then_void_then_void(42) // Unchanged
contract.functions.type_then_option_then_type(42, undefined, 43)
contract.functions.type_then_option_then_option(42, undefined, undefined)
// after
contract.functions.type_then_void_then_type(42, undefined, 43)
contract.functions.type_then_void_then_void(42) // Unchanged
contract.functions.type_then_option_then_type(42, undefined, 43)
contract.functions.type_then_option_then_option(42)
fuel-core@0.32.1 and large contract deployments - #2827
MAX_CONTRACT_SIZE is no longer exported, it should now be fetched from the chain.
// before
import { MAX_CONTRACT_SIZE } from 'fuels';
// after
import { Provider, FUEL_NETWORK_URL } from 'fuels';
const provider = await Provider.create(FUEL_NETWORK_URL);
const { consensusParameters } = provider.getChain();
const maxContractSize = consensusParameters.contractParameters.contractMaxSize.toNumber();
Deprecate FUEL_NETWORK_URL and LOCAL_NETWORK_URL- #2915
Removed FUEL_NETWORK_URL constant.
// before
import { FUEL_NETWORK_URL } from 'fuels';
const provider = await Provider.create(FUEL_NETWORK_URL);
// after
const provider = await Provider.create('https://127.0.0.1:4000/v1/graphql');
Removed LOCAL_NETWORK_URL constant.
// before
import { LOCAL_NETWORK_URL } from 'fuels';
const provider = await Provider.create(LOCAL_NETWORK_URL);
// after
const provider = await Provider.create('https://127.0.0.1:4000/v1/graphql');
Integrate launchTestNode in remaining packages - #2811
Removed generateTestWallet and seedTestWallet utilities.
// before
import { bn } from "@fuel-ts/math";
import {
seedTestWallet,
generateTestWallet,
} from "@fuel-ts/account/test-utils";
const provider = await Provider.create("http://127.0.0.1:4000/v1/graphql");
// seeding
const walletA = Wallet.fromPrivateKey("0x...", provider);
const baseAssetId = provider.getBaseAssetId();
seedTestWallet(wallet, [{ assetId: baseAssetId, amount: bn(100_000) }]);
// generating
const walletB = await generateTestWallet(provider, [[1_000, baseAssetId]]);
// after
import { launchTestNode } from 'fuels/test-utils';
// create two wallets seeded with 100_000 units of the base asset
using launched = await launchTestNode({
walletsConfig: {
count: 2,
amountPerCoin: 100_000,
},
});
const {
wallets: [walletA, walletB]
} = launched;
const balance = await walletA.getBalance() // 100_000
Removed launchNodeAndGetWallets utility.
// before
import { launchNodeAndGetWallets } from 'fuels/test-utils';
const { provider, wallets } = await launchNodeAndGetWallets();
// after
import { launchTestNode } from 'fuels/test-utils';
using launched = await launchTestNode();
const { provider, wallets } = launched;
Renamed AssetId to TestAssetId- #2905
Renamed testing class AssetId to TestAssetId.
// before
import { AssetId } from 'fuels/test-utils';
const [assetA] = AssetId.random();
// after
import { TestAssetId } from 'fuels/test-utils';
const [assetA] = TestAssetId.random();
Adding abi transpiler - #2856
New ABI spec
The SDK now adheres to the new specs introduced via:
- https://github.com/FuelLabs/fuel-specs/pull/596
- https://github.com/FuelLabs/fuel-specs/pull/599
Check these out to understand all its changes.
The class AbiCoder is no longer exported, and the way to do encoding and decoding of specific types is now via the Interface.encodeType and Interface.decodeType methods:
// before
const abi = yourAbi;
const functionArg = abi.functions.inputs[0];
const encoded = AbiCoder.encode(abi, functionArg, valueToEncode);
const decoded = AbiCoder.decode(abi, functionArg, valueToDecode, 0);
// after
import { Interface } from 'fuels';
const abi = yourAbi;
const functionArg = abi.functions.inputs[0];
const abiInterface = new Interface(abi);
const encoded = abiInterface.encodeType(functionArg.concreteTypeId, valueToEncode);
const decoded = abiInterface.decodeType(functionArg.concreteTypeId, valueToDecode);
Previously, you could get a type from the ABI via the Interface.findTypeById. This method has been removed after introducing the new abi specification because the concept of a type has been split into concrete types and metadata types. If you want a specific type, you can get it directly from the ABI.
// before
const abiInterface = new Interface(abi);
// internally this method searched the abi types:
// abi.types.find(t => t.typeId === id);
const type = abiInterface.findTypeById(id);
// after
import { Interface } from 'fuels';
// search the types on the abi directly
const concreteType = abi.concreteTypes.find(ct => ct.concreteTypeId === id);
const metadataType = abiInterface.jsonAbi.metadataTypes.find(mt => mt.metadataTypeId === id);
The JsonAbiArgument type isn't part of the new ABI spec (#596, #599) as such so we stopped exporting it. Its closest equivalent now would be a concrete type because it fully defines a type.
// before
const arg: JsonAbiArgument = {...};
// after
import { Interface } from 'fuels';
type ConcreteType = JsonAbi["concreteTypes"][number]
const arg: ConcreteType = {...};
Read malleable fields from transaction status on subscription - #2962
Removed TransactionResult.gqlTransaction. You can use the TransactionResult.transaction field instead, which has all the data that TransactionResult.gqlTransaction has but already decoded.
// before
const { gqlTransaction } = await new TransactionResponse('your-tx-id').waitForResult();
// after
const { transaction } = await new TransactionResponse('your-tx-id').waitForResult();
Fix assembly process for account transfer operation - #2963
The getTransferOperations helper function now requires an additional baseAssetId parameter.
// before
const transferOperations = getTransferOperations({ inputs, outputs, receipts })
// after
const transferOperations = getTransferOperations({ inputs, outputs, receipts, baseAssetId })
Wrap subscriptions in promise - #2964
// before
const subscription = provider.operations.statusChange({ transactionId });
for await (const response of subscription) { ... }
// after
const subscription = await provider.operations.statusChange({ transactionId });
for await (const response of subscription) { ... }
July 30, 2024
Deploy contract validation - #2796
ErrorCode.INVALID_TRANSACTION_TYPE was migrated to ErrorCode.UNSUPPORTED_TRANSACTION_TYPE.
// before
const code = ErrorCode.INVALID_TRANSACTION_TYPE;
// after
const code = ErrorCode.UNSUPPORTED_TRANSACTION_TYPE;
Remove awaitExecution functionality - #2820
It is no longer possible to submit transactions using the awaitExecution flag and wait for the transaction to be processed at submission:
// before
const response = await account.sendTransaction(transactionRequest, { awaitExecution: true });
// after
const submit = await account.sendTransaction(transactionRequest);
const response = await submit.waitForResult();
Refactored the getTransactionCost method - #2643
Refactored functionality for Provider.getTransactionCost to Account.getTransactionCost and changed estimation parameter from quantitiesToContract to quantities.
// before
const provider = Provider.create(...);
const account = Wallet.generate({ ... }) || new Predicate(...);
const quantities: Array<CoinQuantityLike> = [
{ amount: 1000, assetId: provider.getBaseAssetId() }
];
const cost = provider.getTransactionCost(txRequest, {
resourceOwner: account,
quantitiesToContract: quantities,
})
// after
const provider = Provider.create(...);
const account = Wallet.generate({ ... }) || new Predicate(...);
const quantities: Array<CoinQuantityLike> = [
{ amount: 1000, assetId: provider.getBaseAssetId() }
];
const cost = account.getTransactionCost(txRequest, { quantities });
July 11, 2024
Release v0.92.0
Implement non-blocking contract call - #2692
The call method in the BaseInvocationScope class no longer waits for transaction execution, making it non-blocking. This change affects how transaction responses are handled.
// before
const { logs, value, transactionResult } = await contract.functions.xyz().call()
// after
const { transactionId, waitForResult } = await contract.functions.xyz().call();
const { logs, value, transactionResult } = await waitForResult();
Made deployContract a non-blocking call - #2597
The deployContract method no longer returns the contract instance directly. Instead, it returns an object containing the transactionId , the contractId, and a waitForResult function.
// before
const factory = new ContractFactory(contractByteCode, contractAbi, wallet);
const contract = await factory.deployContract();
const { value } = await contract.functions.xyz().call();
// after
const factory = new ContractFactory(contractByteCode, contractAbi, wallet);
const { waitForResult, transactionId, contractId } = await factory.deployContract();
const { contract, transactionResult } = await waitForResult();
const { value } = await contract.functions.xyz().call();
Implement pagination for Account methods - #2408
// before
const coins = await myWallet.getCoins(baseAssetId);
const messages = await myWallet.getMessages();
const balances = await myWallet.getBalances();
const blocks = await provider.getBlocks();
// after
const { coins, pageInfo } = await myWallet.getCoins(baseAssetId);
const { messages, pageInfo } = await myWallet.getMessages();
const { balances } = await myWallet.getBalances();
const { blocks, pageInfo } = await provider.getBlocks();
/*
The `pageInfo` object contains cursor pagination information one
can use to fetch subsequent pages selectively and on demand.
*/
launchNode.cleanup not killing node in last test of test group - #2718
The killNode and KillNodeParams functionality has been internalized and the method and interface have been deleted so they're no longer exported. It's marked as a breaking change for pedantic reasons and there shouldn't really be any affected users given that they kill nodes via cleanup which is unchanged, so no migration notes are necessary.
Remove InvocationResult from program package - #2652
The classes FunctionInvocationResult, InvocationCallResult, and InvocationResult have been removed. This change will not affect most users as the response for a contract call or script call remains the same; only the type name has changed.
// before
const callResult: FunctionInvocationResult = await contract.functions.xyz().call()
const dryRunResult: InvocationCallResult = await contract.functions.xyz().get()
const dryRunResult: InvocationCallResult = await contract.functions.xyz().dryRun()
const dryRunResult: InvocationCallResult = await contract.functions.xyz().simulate()
// after
const callResult: FunctionResult = await contract.functions.xyz().call()
const dryRunResult: DryRunResult = await contract.functions.xyz().get()
const dryRunResult: DryRunResult = await contract.functions.xyz().dryRun()
const dryRunResult: DryRunResult = await contract.functions.xyz().simulate()
Beta 3-5 Testnet Breaking Change Guide (Archive)
April 30, 2024
Sway
Release: Sway v0.56.0
The std::call_frames::second_param function now returns a u64 instead of a generic type T.
contract_id() has been removed in favor of ContractId::this().
#![allow(unused)] fn main() { /* BEFORE */ let contract_id = contract_id(); /* AFTER */ let contract_id = ContractId::this(); }
call_with_function_selector_vec has been removed in favor of call_with_function_selector.
#![allow(unused)] fn main() { /* BEFORE */ pub fn call_with_function_selector_vec( target: ContractId, function_selector: Vec<u8>, calldata: Vec<u8>, single_value_type_arg: bool, call_params: CallParams ) {...} /* AFTER */ pub fn call_with_function_selector_vec( target: ContractId, function_selector: Bytes // new calldata: Bytes, // new call_params: CallParams ) {...} }
The BASE_ASSET_ID constant has been removed, and AssetId::base_asset_id() is now AssetId::base().
#![allow(unused)] fn main() { /* BEFORE */ let base_asset_id = BASE_ASSET_ID; /* OR */ let base_asset_id = AssetId::base_asset_id(); /* AFTER */ let base_asset_id = AssetId:base(); }
You can no longer access the following:
force_transfer_to_contract()transfer_to_address()mint_to_contract()mint_to_address()
Instead use the transfer(), mint(), and mint_to() functions accordingly.
#![allow(unused)] fn main() { /* BEFORE */ let user = Address:from(address); mint_to_address(user, ZERO_B256, amount); /* AFTER */ mint_to(Identity::Address(user), ZERO_B256, amount); }
The new encoding (encoding v1) is now set by default. If you would like to build your forc project without v1 encoding then run the following command:
forc build --no-encoding-v1
run_external in sway-lib-std has been removed until LDC is stabilized.
Release: Sway v0.55.0
GTF constants along with the following functions have been removed to match the current Fuel VM instruction set:
input_maturity()tx_receipts_root()
tx_gas_price has been renamed tx_tip
#![allow(unused)] fn main() { /* BEFORE */ let gas_price = tx_gas_price(); /* AFTER */ let tip = tx_tip(); }
Release: Sway v0.54.0
The forc-client and forc-tx plugins now take u16 instead of u8 for witness index types.
TS-SDK
Release v0.83.0
BaseAssetId is no longer exported by fuels. It can be fetched from a Provider.
/* BEFORE */
import { BasedAssetId } from "fuels";
/* AFTER */
const provider = await Provider.create(FUEL_NETWORK_URL);
const baseAssetId = provider.getBaseAssetId();
TransactionRequest.addCoinOutput and TransactionRequest.addChangeOutput now requires an assetId, it no longer defaults to the BaseAssetId.
TransactionRequest.fundWithFakeUtxos now requires passing the baseAssetId as a function parameter. This is the only function that is base asset aware, so that it can be used specifically to estimate the transaction cost.
CoinQuantityLike now requires an AssetId. Previously most of it's usages would default to the BaseAssetId, as this must now be fetched, so it must be passed to the type.
/* BEFORE */
let coin: CoinQuantityLike = [1000];
coin = { amount: 1000 };
/* AFTER */
const assetId = "0x..";
let coin: CoinQuantityLike = [1000, assetId];
coin = { amount: 1000, assetId };
gasPrice is calculated by the VM so we do not need use it anymore.
/* BEFORE */
await factory.deployContract({ gasPrice });
/* AFTER */
await factory.deployContract();
PolicyType.GasPrice is now PolicyType.Tip.
The Account.fund function parameters changed. Also the information returned by Provider.getTransactionCost is useful here because it can be passed as the second parameter to Account.fund.
/* BEFORE */
async fund<T extends TransactionRequest>(
request: T,
coinQuantities: CoinQuantity[],
fee: BN,
inputsWithEstimatedPredicates: TransactionRequestInput[],
addedSignatures?: number
): Promise<T>
/* AFTER */
export type EstimatedTxParams = {
maxFee: BN;
estimatedPredicates: TransactionRequestInput[];
addedSignatures: number;
requiredQuantities: CoinQuantity[];
}
async fund<T extends TransactionRequest>(request: T, params: EstimatedTxParams): Promise<T>
GraphQL URL now includes a versioning path: http://127.0.0.1:4000/v1/graphql.
calculateTransactionFee now requires tip, maxGasPerTx, and gasPrice. Also gasUsed is not used anymore.
/* BEFORE */
const { fee } = calculateTransactionFee({
gasUsed,
rawPayload,
consensusParameters: {
gasCosts,
feeParams: {
gasPerByte,
gasPriceFactor,
},
},
});
/* AFTER */
const { fee } = calculateTransactionFee({
gasPrice, // new
tip, // new
consensusParameters: {
maxGasPerTx, // new
gasCosts,
feeParams: {
gasPerByte,
gasPriceFactor,
},
},
rawPayload,
});
Due to forc upgrade v0.52.0 AssetId and EvmAddress property value was renamed to bits
/* BEFORE */
export type EvmAddress = {
value: B256AddressEvm;
};
export type AssetId = {
value: B256Address;
};
/* AFTER */
export type EvmAddress = {
bits: B256AddressEvm;
};
export type AssetId = {
bits: B256Address;
};
Release v0.80.0
Removed unused property usedFee from Provider.getTransactionCost response.
Renamed getAssetId to getMintedAssetId.
Rust SDK
Release v0.58.0
The new encoding is now the default encoding. Use the following command if you would like to run your cargo tests with the legacy encoding.
cargo test --features legacy_encoding
Release v0.57.0
The BASE_ASSET_ID constant has been removed and replaced by a new Provider function.
#![allow(unused)] fn main() { /* BEFORE */ let base_asset_id = BASE_ASSET_ID; /* AFTER */ let base_asset_id = provider.base_asset_id(); }
Config was renamed to NodeConfig
#![allow(unused)] fn main() { /* BEFORE */ let node_config = Config::default(); /* AFTER */ let node_config = NodeConfig::default(); }
FuelService::start() now accepts NodeConfig, ChainConfig, and StateConfig as arguments to startup a node.
#![allow(unused)] fn main() { /* BEFORE */ let server = FuelService::start(Config::default()).await?; /* AFTER */ let server = FuelService::start( NodeConfig::default(), ChainConfig::default(), StateConfig::default(), ) .await?; }
When instantiating ConsensusParameters you must make use of setters.
#![allow(unused)] fn main() { /* BEFORE */ let consensus_parameters = ConsensusParameters { tx_params, fee_params, ..Default::default() }; /* AFTER */ let mut consensus_parameters = ConsensusParameters::default(); consensus_parameters.set_tx_params(tx_params); consensus_parameters.set_fee_params(fee_params); }
Fields now need to be accessed via methods.
#![allow(unused)] fn main() { /* BEFORE */ let chain_id = consensus_parameters.chain_id; /* AFTER */ let chain_id = consensus_parameters.chain_id(); }
The same applies to other parameter structs used when setting up a node, such as TxParameters, ContractParameters, PredicateParameters etc.
The witness_index parameters in CreateTransactionBuilder::with_bytecode_witness_index are now a u16.
NodeInfo no longer has min_gas_price.
CreateTransaction no longer has bytecode_length().
Header no longer has message_receipt_root, but gains:
#![allow(unused)] fn main() { pub message_outbox_root: Bytes32, pub event_inbox_root: Bytes32, pub consensus_parameters_version: u32, pub state_transition_bytecode_version: u32 }
March 27, 2024
Sway
Release Sway v0.52.0
The bytes field on B512 and the value field EvmAddress have been renamed bits, made private, and made accessible via bits().
#![allow(unused)] fn main() { /* BEFORE */ let bytes = myB12.bytes; let value = myEvmAddress.value; /* AFTER */ let bits = myB512.bits(); let bits = myEvmAddress.bits(); }
The fields on U128 have been made private.
#![allow(unused)] fn main() { /* BEFORE */ let zero_u128 = U128 { upper: 0, lower: 0 }; let upper = zero_u128.upper; /* AFTER */ let zero_u128 = U128::from(0, 0); let upper = zero_u128.upper(); }
The fields on StorageKey have been made private and are now accessed via:
new()slot()offset()field_id()
The following heap types have been updated to have private struct variables:
BytesRawBytesVecRawVecString
#![allow(unused)] fn main() { /* BEFORE */ let bytes_ptr = bytes.buf.ptr(); /* AFTER */ let bytes_ptr = bytes.ptr(); }
The value field on AssetId, ContractId, and Address is now private and renamed bits.
#![allow(unused)] fn main() { /* BEFORE */ let value = assetId.value; /* AFTER */ let bits = assetId.bits(); }
predicate_id() has been renamed to predicate_address().
#![allow(unused)] fn main() { /* BEFORE */ let predicate = predicate_id(); /* AFTER */ let predicate = predicate_address(); }
U256 has been removed use the native u256 type instead.
#![allow(unused)] fn main() { /* BEFORE */ let my_U256 = U256::max(); /* AFTER */ let my_u256 = u256::max(); }
TS-SDK
Release v0.79.0
externalLoggedTypes has been removed from the Interface class.
Release v0.77.0
Predicate data is now accepted on the Predicate constructor.
/* BEFORE */
const predicate = new Predicate(bytecode, provider, abi, configurableConstants);
/* AFTER */
const predicate = new Predicate({
bytecode,
abi, // optional
provider,
inputData, // optional
configurableConstants, // optional
});
The setData method has been removed from Predicate. If you want to pass in predicate data after instantiating the Predicate or if you want to use different data than what was passed to the constructor, then you will have to create a new Predicate instance.
Rust SDK
Release v0.56.0
Experimental encoding for logs was added. Use the following command to run your tests with the experimental encoding
cargo test --features experimental
NOTE experimental encoding is now the default encoding in v0.57.0
Configurables structs now need to be instantiated through a ::new(encoder_config) or ::default() method.
#![allow(unused)] fn main() { /* BEFORE */ let configurables = MyContractConfigurables::new().with_STRUCT(my_struct); /* AFTER */ let configurables = MyContractConfigurables::default().with_STRUCT(my_struct); /* OR */ let configurables = MyContractConfigurables::new(encoder_config).with_STRUCT(my_struct); }
Configurables::with_some_string_config(some_string) methods now return a Result<Configurables> instead of Configurables.
Predicates::encode_data now returns a Result<UnresolvedBytes> instead of UnresolvedBytes.
AbiEncoder structs must be instantiated through a ::new(encoder_config) or ::default() method.
#![allow(unused)] fn main() { /* BEFORE */ let encoded = ABIEncoder::encode(&args).resolve(0); /* AFTER */ let encoded = ABIEncoder::default().encode(&args).resolve(0); /* OR */ let encoded = ABIEncoder::new(config).encode(&args).resolve(0); }
EnumVariants are now imported through param_types::EnumVariants.
#![allow(unused)] fn main() { /* BEFORE */ use fuels::types::enum_variants::EnumVariants; /* AFTER */ use fuels::types::param_types::EnumVariants; }
TxPolicies gas_price is replaced with tip
#![allow(unused)] fn main() { /* BEFORE */ let tx_policies = TXPolicies::default().with_gas_price(1); /* AFTER */ let tx_policies = TXPolicies::default().with_tip(1); }
checked_dry_run has been removed from Provider.
dry_run now returns Result<TxStatus> instead of Result<Vec<Receipt>>. The receipts can be taken with tx_status.take_receipts().
TransactionResponse's block_id is replaced with block_height.
estimate_transaction_cost has a new argument block_horizon: Option<u32>.
#![allow(unused)] fn main() { /* BEFORE */ let transaction_cost = contract_instance .methods() .my_contract_call() .estimate_transaction_cost(Some(tolerance)) .await?; /* AFTER */ let transaction_cost = contract_instance .methods() .my_contract_call() .estimate_transaction_cost(tolerance, block_horizon) .await?; }
February 22, 2024
Sway
Release: Sway v0.51.0
You can no longer access private fields in structs.
#![allow(unused)] fn main() { /* BEFORE */ let private = my_struct.private // just a warning /* AFTER */ let private = my_struct.private // ERROR let private = my_struct.private() // you must create a function to access private variables }
The Never type is now !.
#![allow(unused)] fn main() { /* BEFORE */ let x: NEVER = { return 123 }; /* AFTER */ let x: ! = { return 123 }; }
Release: Sway v0.50.0
Configurables are now forbidden in const expressions.
// Not allowed script; configurable { VALUE: u64 = 42, } fn main() { const CONSTANT: u64 = VALUE; }
Struct fields are now private by default. You must explicitly mark a field public.
#![allow(unused)] fn main() { /* BEFORE */ pub struct Struct { public_field: u8, } /* AFTER */ pub struct Struct { pub public_field: u8, private_field: u8, } }
The From trait has been redesigned into the From/Into rust-like trait pair.
#![allow(unused)] fn main() { /* BEFORE */ impl From<b256> for Address { fn from(bits: b256) -> Self { Self { value: bits } } fn into(self) -> b256 { self.value } } let address = Address::from(ZERO_B256); let b256_data = address.into(); /* AFTER */ impl From<b256> for Address { fn from(bits: b256) -> Self { Self { value: bits } } } impl From<Address> for b256 { fn from(address: Address) -> b256 { address.value } } let address = Address::from(ZERO_B256); let b256_data: b256 = address.into(); let address: Address = b256_data.into(); }
TS-SDK
Release: v0.74.0
Provider has been removed from WalletManager types.
/* Before */
const vault = new MnemonicVault({
secret: mnemonic,
provider,
});
/* After */
const vault = new MnemonicVault({
secret: mnemonic,
});
The Account and account related packages have been restructured. Anything imported from the following packages will now be imported from @fuel-ts/account
@fuel-ts/hdwallet@fuel-ts/mnemonic@fuel-ts/predicate@fuel-ts/providers@fuel-ts/signer@fuel-ts/wallet-manager@fuel-ts/wallet@fuel-ts/wordlists
February 5, 2024 (Beta 5)
Sway
Release: Sway v0.49.2
Numerous elements in the standard library have undergone changes. The token.sw file has been renamed to asset.sw, impacting the transfer() and mint_to() functions. This modification aims to bring about greater consistency across all functions related to asset management.
/* BEFORE - v0.46.0 */
use std::{
token::transfer
};
/* AFTER - v0.49.2 */
use std::{
asset::transfer
};
The instructions LW (Load Word) and SW (Store Word) are now replaced with LB (Load Byte) and SB (Store Byte), specifically for smaller data types like u8. This adjustment allows these types to be contained within a single byte, rather than occupying a full word. However, for other data types such as u16, the original instruction format remains unchanged.
/* BEFORE - v0.46.0 */
sw output r1 i0;
/* AFTER - v0.49.2 */
sb output r1 i0;
DEFAULT_SUB_ID has been introduced to improve UX. It is equivalent to the ZERO_B256 constant.
/* BEFORE - v0.46.0 */
use std::call_frames::contract_id;
fn foo(other_contract: ContractId) {
let other_asset = AssetId::default(other_contract);
let my_asset = AssetId::default(contract_id());
}
/* AFTER - v0.49.2 */
fn foo(other_contract: ContractId) {
let other_asset = AssetId::new(other_contract, DEFAULT_SUB_ID);
let my_asset = AssetId::default();
}
The Eq trait now exists for Option.
/* BEFORE - v0.46.0 */
let option1: Option<u64> = Some(5);
let option2: Option<u64> = Some(5);
match (option1, option2) {
(Some(a), Some(b)) => a == b,
(None, None) => true,
_ => false,
}
/* AFTER - v0.49.2 */
let option1: Option<u64> = Some(5);
let option2: Option<u64> = Some(5);
if option1 == option2 {
return true
} else {
return false
}
The standard library tx introduces several new functions including tx_max_fee(), tx_witness_limit(), script_gas_limit(), and policies(). tx_gas_limit() has been deprecated to support the new TxPolicy, replacing TxParameters.
/* BEFORE - v0.46.0 */
fn get_tx_gas_limit() -> u64;
/* AFTER - v0.49.2 */
fn get_script_gas_limit() -> u64;
The existing functions inside the standard library tx, including tx_gas_price() and tx_maturity(), now return Option<u64> and Option<u32> respectively, instead of just u64 and u32.
/* BEFORE - v0.46.0 */
fn get_tx_maturity() -> u32 {
tx_maturity()
}
/* AFTER - v0.49.2 */
fn get_tx_maturity() -> u32 {
tx_maturity().unwrap()
}
Along with these changes, GTF opcodes have been updated in the following standard libraries.
Byte conversions and array conversions for u256, u64, u32, u16, and b256 have been introduced into the standard library.
/* AFTER - v0.49.2 */
fn foo() {
let x: u16 = 513;
let result = x.to_le_bytes();
assert(result[0] == 1_u8);
assert(result[1] == 2_u8);
}
/* AFTER - v0.49.2 */
fn foo() {
let x: u16 = 513;
let result = x.to_le_bytes();
assert(result.get(0).unwrap() == 1_u8);
assert(result.get(1).unwrap() == 2_u8);
}
Power uses a u32 instead of self
/* BEFORE - v0.46.0 */
assert(2u16.pow(2u16) == 4u16);
/* AFTER - v0.49.2 */
assert(2u16.pow(2u32) == 4u16);
TS SDK
Release: TS SDK v0.73.0
Several fuel-core configuration-related options have been removed from the LaunchNodeOptions. These include: chainConfigPath, consensusKey, useInMemoryDb, and poaInstant. These options can now only be passed through the args property.
/* BEFORE - v0.60.0 */
const { cleanup, ip, port } = await launchNode({
chainConfigPath,
consensusKey = "0xa449b1ffee0e2205fa924c6740cc48b3b473aa28587df6dab12abc245d1f5298",
args: defaultFuelCoreArgs,
});
/* AFTER - v0.73.0 */
const { cleanup, ip, port } = await launchNode({
args: ["--poa-instant", "false", "--poa-interval-period", "400ms"],
});
Contract calls requires gasLimit and gasPrice to be specified in txParams().
/* BEFORE - v0.60.0 */
let resp = await contract.functions.count().simulate();
/* AFTER - v0.73.0 */
let resp = await contract.functions
.count()
.txParams({ gasPrice: 1, gasLimit: 100_000 })
.simulate();
chainInfoCache and nodeInfoCache are now private methods, to prevent users from accessing invalid cached information after it becomes stale.
/* BEFORE - v0.60.0 */
Provider.chainInfoCache[FUEL_NETWORK_URL];
Provider.nodeInfoCache[FUEL_NETWORK_URL];
/* AFTER - v0.73.0 */
provider.getChain();
provider.getNode();
The switchURL() method, used to update the URL for the provider, is now named connect().
/* BEFORE - v0.60.0 */
await provider.switchUrl(altProviderUrl);
/* AFTER - v0.73.0 */
await provider.connect(altProviderUrl);
Support for new Sway types has been introduced with:
- Bytes
/* AFTER - v0.73.0 */
const bytes = [40, 41, 42];
const { value } = await contract.functions.bytes_comparison(bytes).simulate();
- Raw Slices
/* AFTER - v0.73.0 */
const rawSlice = [40, 41, 42];
const { value } = await contract.functions
.raw_slice_comparison(rawSlice)
.simulate();
- StdString
/* AFTER - v0.73.0 */
const stdString = "Hello World";
const { value } = await contract.functions
.string_comparison(stdString)
.simulate();
Typegen attempts to resolve, auto-load, and embed the Storage Slots for your Contract within the MyContract__factory class. However, you can override this, along with other options, when calling the deployContract method:
/* AFTER - v0.73.0 */
import storageSlots from "../contract/out/debug/storage-slots.json";
const contract = await MyContract__factory.deployContract(bytecode, wallet, {
storageSlots,
});
concat, arrayify, and hexlify have been introduced to the utils to replace their respective functions from the ethers library, avoiding the reexporting of ethers functions.
/* BEFORE - v0.60.0 */
import { concat, arrayify, hexlify } from "@ethersproject/bytes";
const someBytes = concat([
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
new Uint8Array([7, 8, 9]),
]);
const someHex = hexlify(new Uint8Array([0, 1, 2, 3]));
const someArray = arrayify(new Uint8Array([0, 1, 2, 3]));
/* AFTER - v0.73.0 */
import { concat, arrayify, hexlify } from "@fuel-ts/utils";
const someBytes = concat([
new Uint8Array([1, 2, 3]),
new Uint8Array([4, 5, 6]),
new Uint8Array([7, 8, 9]),
]);
const someHex = hexlify(new Uint8Array([0, 1, 2, 3]));
const someArray = arrayify(new Uint8Array([0, 1, 2, 3]));
Address types can no longer be used directly to represent a b256 and must instead use the toB256() conversion method.
/* BEFORE - v0.60.0 */
const addressId = {
value: userWallet.address,
};
tokenContract.functions
.transfer_coins_to_output(addressId, assetId, amount)
.call();
/* AFTER - v0.73.0 */
const addressId = {
value: userWallet.address.toB256(),
};
tokenContract.functions
.transfer_coins_to_output(addressId, assetId, amount)
.call();
The Account class's fund() method now takes in two new parameters: quantities and fee, of types CoinQuantity[] and BN, respectively. These can be derived from the provider's getTransactionCost() method.
/* BEFORE - v0.60.0 */
await wallet.fund(transactionRequest);
/* AFTER - v0.73.0 */
const { maxFee, requiredQuantities } = await provider.getTransactionCost(
transactionRequest
);
await wallet.fund(transactionRequest, quantities, fee);
The provider's getTransactionCost now breaks down its old fee into minFee, usedFee, and maxFee, based on the actual calculation of the transaction. Additionally, requiredQuantities, receipts, minGas, and maxGas, of types coinQuantity[], TransactionResultReceipt[], BN, and BN respectively, have also been introduced to improve the granularity of cost estimation.
/* BEFORE - v0.60.0 */
const { fee } = await this.account.provider.getTransactionCost(
transactionRequest
);
/* AFTER - v0.73.0 */
const {
requiredQuantities,
receipts,
minGas,
maxGas,
minFee,
maxFee,
usedFee,
} = await this.account.provider.getTransactionCost(transactionRequest);
The getTransferOperations function now takes in a receipts parameter as well, ensuring that contract transactions return the transfer asset.
/* BEFORE - v0.60.0 */
const operations = getTransferOperations({ inputs: [], outputs: [] });
/* AFTER - v0.73.0 */
const operations = getTransferOperations({
inputs: [],
outputs: [],
receipts: [],
});
The predicate introduces a new getTransferTxId, a method to calculate the transaction ID for a Predicate.transfer transaction.
/* AFTER - v0.73.0 */
const txId = await predicate.getTransferTxId(address, amount, BaseAssetId, {
gasPrice,
});
The deployContract method contains a new parameter, storageSlotsPath, to avoid issues that may arise if storage slots are not auto-loaded. Without auto-loading, some contracts will revert due to improper or missing initialization of storage slots.
/* BEFORE - v0.60.0 */
const assetId = BaseAssetId;
/* AFTER - v0.73.0 */
const assetId: AssetId = { value: BaseAssetId };
AssetId has been introduced to match the Sway standard library as a Struct wrapper around an inner Bits256 value.
Rust SDK
Release: Rust SDK v0.55.0
The sign_message() and sign_transaction functions in the Signer trait have been consolidated into a single method, now simply named sign.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let signature1: B512 = wallet.sign_message(data_to_sign).await?.as_ref().try_into()?; /* AFTER - v0.55.0 */ let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?; }
The function check_without_signatures in the Transaction trait has been renamed to check. This updated check function retains its original capabilities and now includes the additional feature of checking with signatures.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ tx.check_without_signatures(chain_info.latest_block.header.height, self.consensus_parameters())?; /* AFTER - v0.55.0 */ tx.check(chain_info.latest_block.header.height, self.consensus_parameters())?; }
The typo in the add_witnessses function name under the Account trait has been fixed and is now add_witnesses.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ account.add_witnessses(&mut tb); /* AFTER - v0.55.0 */ account.add_witnesses(&mut tb)?; }
Use of Message, PublicKey, SecretKey and Signature can be found inside fuels::crypto:: now.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ use fuels::accounts::fuel_crypto::SecretKey; /* AFTER - v0.55.0 */ use fuels::crypto::SecretKey, }
The submit_and_await_commit() function now returns a TxStatus instead of a TxId.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let tx_id = self.client.submit_and_await_commit(&tx.clone().into()).await?.into(); /* AFTER - v0.55.0 */ let tx_status = self.client.submit_and_await_commit(&tx.clone().into()).await?.into(); }
When constructing a transaction, the provider already possesses all the necessary information, rendering NetworkInfo and all its related functions and methods obsolete. Consequently, ScriptTransactionBuilder::new, CreateTransactionBuilder::new, and Provider::new have been removed for ::default().
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ use fuels_core::types::transaction_builders::{DryRunner, NetworkInfo} ScriptTransactionBuilder::new(network_info) /* AFTER - v0.55.0 */ use fuels_core::types::transaction_builders::{DryRunner} ScriptTransactionBuilder::default() }
In Sway, U256 has been deprecated in favor of u256. It is no longer supported in the SDK. Usage of U256 will now result in a runtime error.
TxPolicies supersedes TxParameters.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let tx_parameters = TxParameters::default() /* AFTER - v0.55.0 */ let tx_policies = TxPolicies::default() }
Three new optional fields have been introduced in TxPolicies:
WitnessLimit, which sets a new restriction for transaction witnesses by introducing a limit on the maximum byte size of witnesses in transactions.MaxFee, which sets an upper limit on the transaction fee that a user is willing to pay.ScriptGasLimit, which no longer constrains predicate execution time but exclusively limits the gas limit of scripts. If this field is not set, the SDK will estimate gas consumption and set it automatically.
Additionally, GasPrice and Maturity fields within TxPolicies are now optional parameters.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let tx_parameters = TxParameters::new(gas_price, gas_limit, maturity) /* AFTER - v0.55.0 */ let tx_policies = TxPolicies::new(Some(gas_price), Some(witness_limit), Some(maturity), Some(max_fee), Some(script_gas_limit)) }
TxPolicy Pitfalls
- If the
max_feeis greater thanpolicies.max_fee, then the transaction will be rejected. - If the
witnesses_sizeis greater thanpolicies.witness_limit, then the transaction will be rejected.
The predicate's get_message_proof now uses nonce instead of msg_id.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let proof = predicate.try_provider()? .get_message_proof(&tx_id, &msg_id, None, Some(2)) /* AFTER - v0.55.0 */ let proof = predicate.try_provider()? .get_message_proof(&tx_id, &msg_nonce, None, Some(2)) }
When using local chain configs, the manual_blocks_enabled option is replaced by the new debug flag. Additionally, with local_node() being deprecated in favor of default(), the options utxo_validation and manual_blocks_enabled are enabled by default for the test providers.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ let config = Config { utxo_validation: true, manual_blocks_enabled: true, ..Config::local_node() }; /* AFTER - v0.55.0 */ let config = Config { ..Config::default() }; }
When using transaction_builders, the BuildableTransaction trait must be in scope.
#![allow(unused)] fn main() { /* BEFORE - v0.48.0 */ use fuels_core::{ types::{ transaction_builders::{TransactionBuilder, ScriptTransactionBuilder}, }, }; /* AFTER - v0.55.0 */ use fuels_core::{ types::{ transaction_builders::{BuildableTransaction, ScriptTransactionBuilder}, }, }; }
October 2, 2023
TS SDK
Release: TS SDK v0.60.0
Provider is used so widely in our SDK, there are multiple breaking changes that we need to be aware of and need to communicate to our users:
/* BEFORE - v0.57.0 */
const provider = new Provider(url);
/* AFTER - v0.60.0 */
const provider = await Provider.create(url);
All of these methods now require a Provider to be passed in:
Wallet Methods
Some of these methods used to accept a URL instead of a Provider object. Note that the provider parameter has to be a Provider object now.
const provider = await Provider.create(url);
/* BEFORE - v0.57.0 */
WalletUnlocked.fromSeed(seed, path);
WalletUnlocked.fromMnemonic(mnemonic, path, passphrase);
WalletUnlocked.fromExtendedKey(extendedKey);
await WalletUnlocked.fromEncryptedJson(jsonWallet, password);
Wallet.fromAddress(address);
Wallet.fromPrivateKey(pk);
Wallet.generate();
/* AFTER - v0.60.0 */
WalletUnlocked.fromSeed(seed, provider, path);
WalletUnlocked.fromMnemonic(mnemonic, provider, path, passphrase);
WalletUnlocked.fromExtendedKey(extendedKey, provider);
await WalletUnlocked.fromEncryptedJson(jsonWallet, password, provider);
Wallet.fromAddress(address, provider);
Wallet.fromPrivateKey(pk, provider);
Wallet.generate({ provider });
'Account' Class
/* BEFORE - v0.57.0 */
const account = new Account(address);
/* AFTER - v0.60.0 */
const account = new Account(address, provider);
PrivateKeyVault
These are the options that are accepted by the PrivateKeyVault constructor. provider is now a required input.
/* BEFORE - v0.57.0 */
interface PkVaultOptions {
secret?: string;
accounts?: Array<string>;
}
/* AFTER - v0.60.0 */
interface PkVaultOptions {
secret?: string;
accounts?: Array<string>;
provider: Provider;
}
MnemonicVault
/* BEFORE - v0.57.0 */
interface MnemonicVaultOptions {
secret?: string;
accounts?: Array<string>;
}
/* AFTER - v0.60.0 */
interface MnemonicVaultOptions {
secret?: string;
accounts?: Array<string>;
provider: Provider;
}
WalletManager
/* BEFORE - v0.57.0 */
export type VaultConfig = {
type: string;
title?: string;
secret?: string;
};
/* AFTER - v0.60.0 */
export type VaultConfig = {
type: string;
title?: string;
secret?: string;
provider: Provider;
};
Predicates
The provider is no longer optional. Note the change in parameter order, and that chainId is no longer required to be passed.
/* BEFORE - v0.57.0 */
const predicate = new Predicate(bytes, chainId, jsonAbi);
/* AFTER - v0.60.0 */
const predicate = new Predicate(bytes, provider, jsonAbi);
September 18, 2023
Sway
Release: Sway v0.46.0
From now on, string literals produce the str slice type instead of the string array type. To convert between string arrays and slices, you can use the newly provided intrinsics.
/* BEFORE - v0.45.0 */
let my_string: str[4] = "fuel";
/* AFTER - v0.46.0 */
let my_string: str = "fuel";
If you use a function that needs a specific trait and you don't import that trait, the compiler now will raise an error. This is because the compiler isn't aware of the trait in the current context.
For the example below you would now get an error if the Hash trait for u64 isn't imported. To solve this, ensure you import the "Hash" trait.
/* BEFORE - v0.45.0 */
storage {
item_map: StorageMap<u64, Item> = StorageMap {},
}
/* AFTER - v0.46.0 */
use std::{
hash::Hash,
};
storage {
item_map: StorageMap<u64, Item> = StorageMap {},
}
TS SDK
Release: TS SDK v0.57.0
The addResourceInputsAndOutputs() function has been renamed to addResources(), streamlining its name.
/* BEFORE - v0.55.0 */
request.addResourceInputsAndOutputs(resources);
/* AFTER - v0.57.0 */
request.addResources(resources);
Similarly, addPredicateResourcesInputsAndOutputs() is now more concisely known as addPredicateResources().
The reason we have a distinct method for adding predicate resources is that the creation of predicate inputs mandates the presence of both the predicate's bytes and data bytes. With these methods, there's no longer a need to manually create and set up an instance of a ScriptTransactionRequest, simplifying the process further.
/* BEFORE - v0.55.0 */
const predicateInputs: TransactionRequestInput[] = predicateUtxos.map(
(utxo) => ({
id: utxo.id,
type: InputType.Coin,
amount: utxo.amount,
assetId: utxo.assetId,
owner: utxo.owner.toB256(),
txPointer: "0x00000000000000000000000000000000",
witnessIndex: 0,
maturity: 0,
predicate: predicate.bytes,
predicateData: predicate.predicateData,
})
);
/* AFTER - v0.57.0 */
request.addPredicateResources(
predicateUtxos,
predicate.bytes,
predicate.predicateData
);
Rust SDK
Release: Rust SDK v0.48.0
The function calculate_base_amount_with_fee() currently returns a value of type Option<64>.
#![allow(unused)] fn main() { /* BEFORE - v0.47.0 */ let new_base_amount = calculate_base_amount_with_fee(&tb, &consensus_parameters, previous_base_amount) /* AFTER - v0.48.0 */ let new_base_amount = calculate_base_amount_with_fee(&tb, &consensus_parameters, previous_base_amount)? }
The function calculate_base_amount_with_fee() now returns a value of type Result<Option<TransactionFee>> instead of Option<TransactionFee>.
#![allow(unused)] fn main() { /* BEFORE - v0.47.0 */ let transaction_fee = tb.fee_checked_from_tx(consensus_params).expect("Error calculating TransactionFee"); /* AFTER - v0.48.0 */ let transaction_fee = tb.fee_checked_from_tx(consensus_params)?.ok_or(error!(InvalidData, "Error calculating TransactionFee"))?; }
Storage slots are now automatically loaded in when using the default configuration.
#![allow(unused)] fn main() { /* BEFORE - v0.47.0 */ let storage_config = StorageConfiguration::load_from("out/debug/contract-storage_slots.json").unwrap(); let load_config = LoadConfiguration::default().with_storage_configuration(storage_config); let id = Contract::load_from( "./out/debug/contract.bin", load_config, ) .unwrap() .deploy(&wallet, TxParameters::default()) .await .unwrap(); /* AFTER - v0.48.0 */ let id = Contract::load_from( "./out/debug/contract.bin", LoadConfiguration::default(), ) .unwrap() .deploy(&wallet, TxParameters::default()) .await .unwrap(); }
Verified Assets
Using this section
You can find the current list of verified assets maintained by Fuel here: verified-assets.json
Projects are welcome to use this information, but please note that it is provided at your own risk.
Additionally, you can download the latest asset information and icons in a single archive. This is useful if you want to locally cache the list or include it in a release pipeline for your tools and libraries: verified-assets.zip
For more information, please visit the verified assets repository here.
Ethereum Sepolia Testnet
| Name | Address | Decimals |
|---|---|---|
Ethereum | 18 | |
Fuel | 0xd7fc4e8fb2c05567c313f4c9b9e07641a361a550 | 9 |
USDC | 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 | 6 |
USDe | 0xc6387efad0f184a90b34f397c3d6fd63135ef790 | 18 |
sUSDe | 0xb8f4f4eafc1d2a3c0a4d519bbf1114c311cc9b1b | 18 |
wstETH | 0xB82381A3fBD3FaFA77B3a7bE693342618240067b | 18 |
Ethereum Foundry
| Name | Address | Decimals |
|---|---|---|
Ethereum | 18 |
Ethereum L1
Fuel Devnet
| Name | Asset ID | Contract Address | Decimals |
|---|---|---|---|
Ethereum | 0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07 | 9 |
Fuel Testnet
| Name | Asset ID | Contract Address | Decimals |
|---|---|---|---|
Ethereum | 0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07 | 9 | |
Fuel | 0x324d0c35a4299ef88138a656d5272c5a3a9ccde2630ae055dacaf9d13443d53b | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 | 9 |
USDC | 0xc26c91055de37528492e7e97d91c6f4abe34aae26f2c4d25cff6bfe45b5dc9a9 | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 | 6 |
USDe | 0x86a1beb50c844f5eff9afd21af514a13327c93f76edb89333af862f70040b107 | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 | 9 |
sUSDe | 0xd2886b34454e2e0de47a82d8e6314b26e1e1312519247e8e2ef137672a909aeb | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 | 9 |
wstETH | 0xb42cd9ddf61898da1701adb3a003b0cf4ca6df7b5fe490ec2c295b1ca43b33c8 | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 | 9 |
Fuel Mainnet
| Name | Asset ID | Contract Address | Decimals |
|---|---|---|---|
Ethereum | 0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07 | 9 | |
Fuel | 0x1d5d97005e41cae2187a895fd8eab0506111e0e2f3331cd3912c15c24e3c1d82 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
WETH | 0xa38a5a8beeb08d95744bc7f58528073f4052b254def59eba20c99c202b5acaa3 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
weETH | 0x239ed6e12b7ce4089ee245244e3bf906999a6429c2a9a445a1e1faf56914a4ab | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
rsETH | 0xbae80f7fb8aa6b90d9b01ef726ec847cc4f59419c4d5f2ea88fec785d1b0e849 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
rETH | 0xf3f9a0ed0ce8eac5f89d6b83e41b3848212d5b5f56108c54a205bb228ca30c16 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
wbETH | 0x7843c74bef935e837f2bcf67b5d64ecb46dd53ff86375530b0caf3699e8ffafe | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
rstETH | 0x962792286fbc9b1d5860b4551362a12249362c21594c77abf4b3fe2bbe8d977a | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
amphrETH | 0x05fc623e57bd7bc1258efa8e4f62b05af5471d73df6f2c2dc11ecc81134c4f36 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
Manta mBTC | 0xaf3111a248ff7a3238cdeea845bb2d43cf3835f1f6b8c9d28360728b55b9ce5b | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
Manta mETH | 0xafd219f513317b1750783c6581f55530d6cf189a5863fd18bd1b3ffcec1714b4 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
Manta mUSD | 0x89cb9401e55d49c3269654dd1cdfb0e80e57823a4a7db98ba8fc5953b120fef4 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
pumpBTC | 0x0aa5eb2bb97ca915288b653a2529355d4dc66de2b37533213f0e4aeee3d3421f | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 8 |
FBTC | 0xb5ecb0a1e08e2abbabf624ffea089df933376855f468ade35c6375b00c33996a | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 8 |
SolvBTC | 0x1186afea9affb88809c210e13e2330b5258c2cef04bb8fff5eff372b7bd3f40f | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
SolvBTC.BBN | 0x7a4f087c957d30218223c2baaaa365355c9ca81b6ea49004cfb1590a5399216f | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
Mantle mETH | 0x642a5db59ec323c2f846d4d4cf3e58d78aff64accf4f8f6455ba0aa3ef000a3b | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
sDAI | 0x9e46f919fbf978f3cad7cd34cca982d5613af63ff8aab6c379e4faa179552958 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
USDT | 0xa0265fb5c32f6e8db3197af3c7eb05c48ae373605b8165b6f4a51c5b0ba4812e | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 6 |
USDC | 0x286c479da40dc953bddc3bb4c453b608bba2e0ac483b077bd475174115395e6b | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 6 |
USDe | 0xb6133b2ef9f6153eb869125d23dcf20d1e735331b5e41b15a6a7a6cec70e8651 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
sUSDe | 0xd05563025104fc36496c15c7021ad6b31034b0e89a356f4f818045d1f48808bc | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
rsUSDe | 0x78d4522ec607f6e8efb66ea49439d1ee48623cf763f9688a8eada025def033d9 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
wstETH | 0x1a7815cc9f75db5c24a5b0814bfb706bb9fe485333e98254015de8f48f84c67b | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
ezETH | 0x91b3559edb2619cde8ffb2aa7b3c3be97efd794ea46700db7092abeee62281b0 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
pzETH | 0x1493d4ec82124de8f9b625682de69dcccda79e882b89a55a8c737b12de67bd68 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
Re7LRT | 0xf2fc648c23a5db24610a1cf696acc4f0f6d9a7d6028dd9944964ab23f6e35995 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
steakLRT | 0x4fc8ac9f101df07e2c2dec4a53c8c42c439bdbe5e36ea2d863a61ff60afafc30 | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 | 9 |
USDF | 0x33a6d90877f12c7954cca6d65587c25e9214c7bed2231c188981c7114c1bdb78 | 9 |
Verified Contracts
Ethereum Mainnet
| Contract Name | Contract Address |
|---|---|
| FuelChainState | 0xf3D20Db1D16A4D0ad2f280A5e594FF3c7790f130 |
| FuelERC20GatewayV4 | 0xa4cA04d02bfdC3A2DF56B9b6994520E69dF43F67 |
| FuelMessagePortal | 0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf |
Fuel Mainnet
| Contract Name | Contract Address |
|---|---|
| FuelL2BridgeId | 0x4ea6ccef1215d9479f1024dff70fc055ca538215d2c8c348beddffd54583d0e8 |
Ethereum Testnet
| Contract Name | Contract Address |
|---|---|
| FuelChainState | 0xf38F1e65adc58fc74BaaA132f645Aa5307F2d304 |
| FuelERC20GatewayV4 | 0xd1d5a4379dccC46D5c8D1c6c2656ce705698e359 |
| FuelMessagePortal | 0x01855B78C1f8868DE70e84507ec735983bf262dA |
Fuel Testnet
| Contract Name | Contract Address |
|---|---|
| FuelL2BridgeId | 0xd02112ef9c39f1cea7c8527c26242ca1f5d26bcfe8d1564bee054d3b04175471 |
Fuel Bridge Security Council
Threshold Requirement: 10/13
| Entity Name | Address |
|---|---|
| Fuel Labs | 0x958470a2ADe72b7a01A2e160F3286767b9623Ad7 |
| Fuel Labs | 0x81ACA96D4Ae0932d2F3463a043392efcCB1F05b6 |
| Fuel Labs | 0x796C3f536C6bf5CB7661C9A0570da0e1ECD303Dd |
| Stefan George | 0x9F7dfAb2222A473284205cdDF08a677726d786A0 |
| Fuel Labs | 0xC8Bd2Ead61e54C53C5A1836352c29F10383FBad2 |
| Razzle Dazzel Technologies | 0x515Fa9b26E195a043582377F51F9A9bAD2D10c7d |
| Informal Systems | 0x76707a7F4b40ecFCc7431A3D7345Ef597ee7e306 |
| Delphi Labs | 0xd4c29D8ddC7D3E326030270f35d9FD4973AbBE09 |
| Chorus One | 0x5F5e0C904153789a9E978c286180b4191950d886 |
| Simply Staking | 0x446f9d40cA491cf0788dacCAc4D16d5d8B4015Cc |
| Alpha Analytics | 0xAA52e167e8Ad426054DCF0fd5BD5481348F4FfC8 |
| Kintsugi Technologies | 0x8a34B78Feb23b97b5ccDf83D9aDC7669C34D346F |
| CryptoCrew Validators | 0x7cdbF64f57f0D623D924d2b4c17664c1Cd9f93d4 |
Tokenomics
With the introduction of FUEL and its total initial supply of 10 billion tokens, we aim to create a level playing field that ensures fair access for both existing members and newcomers to our growing ecosystem. To acknowledge and reward our long-standing friends, builders, and users, 20% of the initial supply will be distributed to the community, with eligibility determined by factors such as participation in the Fuel Points Program and our incentivized testnet. In total, over 51% of all FUEL tokens will ultimately be allocated to the community, the broader ecosystem, and ongoing research and development efforts, reflecting our commitment to inclusivity and shared growth.

| Group | Details | Amount |
|---|---|---|
| Community Expansion | Used for incentives, programs, campaigns and activations for the Fuel community and expansion. | 2.00 billion |
| Ecosystem and R&D | Used to establish the sequencing network, and for ecosystem development and Fuel technology R&D. | 1.55 billion |
| Ecosystem and R&D 24 | Same as above. These locked tokens can be staked during the 24 month block-by-block linear release. Staking rewards will only be used for ecosystem development efforts and to enable L2 level incentives. | 1.55 billion |
| Contributors 24 | Includes past and present Fuel contributors. | 0.60 billion |
| Contributors 48 | Core project contributors. | 0.98 billion |
| Purchasers | Token purchasers from 2020 to 2022. | 3.31 billion |
Inflation
Inflation will be 3% annually. Note that inflation and Fuel economics are configured by the sequencer validator set.
Unlocks
Note: Team and Purchasers can't stake locked tokens.

| Group | Details | Vesting Schedule |
|---|---|---|
| Community | Used for incentives, programs, campaigns and activations for the Fuel community and expansion. | Immediate |
| Ecosystem and R&D | Used to establish the sequencing network, and for ecosystem development and Fuel technology R&D. | Immediate |
| Ecosystem and R&D 24 | Same as above. These locked tokens can be staked during the 24 month block-by-block linear release. Staking rewards will only be used for ecosystem development efforts and to enable L2 level incentives. | 24 month linear release |
| Contributors 24 | Includes past and present Fuel contributors. | 24 month linear release |
| Contributors 48 | Core project contributors. | 48 month linear release |
| Purchasers | Token purchasers from 2020 to 2022. | 24 month linear release |
How to Upgrade Fuel V1 to FUEL
The Fuel V1 token was the previous iteration of the FUEL token. The initial supply of Fuel V1 tokens was 100 million. The supply of the current FUEL token is 10 billion. Fuel V1 tokens can accordingly be upgraded at a ratio of 1:100.
Please visit app.fuel.network/upgrade to upgrade your Fuel V1 tokens to FUEL tokens. Below is a short help guide:
Connecting an EVM Wallet
Fuel V1 grants exist only on the Ethereum mainnet. Please connect an EVM wallet with a token grant or Fuel V1 tokens using the button in the top-right corner.

Claiming a Token Grant
If you have a token grant, the amount of unclaimed tokens will be displayed, along with a button to claim the tokens into your wallet. If you already have Fuel V1 tokens ready to upgrade, you can skip this step.

Upgrade Fuel V1 Tokens to Fuel Tokens
Once you have Fuel V1 tokens in your wallet, click the "Upgrade to FUEL" button to start the upgrading process. This will take you to two screens.

The first screen is to approve the amount of tokens to be upgraded.

The second screen is to complete upgrading of Fuel V1 tokens to FUEL tokens.

That's it! You can now deposit and delegate your tokens to start earning rewards. Since your tokens are on the Ethereum mainnet, you have two options:
-
Stake on Ethereum:
Use app.fuel.network/staking/on-ethereum to stake your tokens on the shared sequencer network from Ethereum. Note that this interface differs from the one used for tokens on Fuel Ignition. Follow the instructions provided in How to Stake on Mainnet Ethereum.
-
Bridge and Stake on Fuel Ignition:
Alternatively, you can withdraw and bridge your tokens to Fuel Ignition to benefit from lower costs and higher speeds. Use the Fuel Bridge to transfer your tokens, then stake them using app.fuel.network/staking/on-fuel. Follow the instructions provided in the How to Stake on Fuel Ignition guide.

Note: It may take some time for your balance to appear on the shared sequencer network. Please don’t panic—your balance will update soon.
Claim Genesis Drop
Please visit app.fuel.network/earn-points/drop/ to claim your FUEL airdrop. Below is a brief help guide to assist you.
Check Eligibility
Start by connecting all accounts eligible for Phase 1 of the FUEL airdrop. This includes GitHub accounts, Fuel wallets (both native and non-native), and Ethereum wallets.

You connect all three wallet types at the same time and view the claimable amount for each individual account.

Note that any eligible Ethereum accounts you connect will automatically load their corresponding eligible Fuel accounts. If rewards were earned using a non-Fuel wallet, they will still appear in the Fuel Wallet where the tokens were bridged.

Claim
To start the claiming process, click “Join the Claim Queue.”

Once you have joined the queue, you will have exactly 10 minutes to claim your FUEL tokens from each of your eligible accounts or else you will have to requeue.

Please carefully read over the terms of service before proceeding to claim your tokens. Once you’re ready, simply click “Claim.”

When claiming through an Ethereum wallet, you can connect a Fuel wallet to receive your tokens. Plus, as a bonus, if you don't have any ETH on Fuel Ignition, you’ll get a small amount of gas to help you claim your Fuel tokens!

Congratulations and thank you for being a part of the Fuel community! Now, please head over to Fuel Ignition and follow this guide to stake your tokens.
How to Stake on Fuel
Please visit app.fuel.network/staking/on-fuel to stake your tokens on the Fuel Shared Sequencer Network through Fuel Ignition. Below, you'll find a helpful guide to get started.
Connect Fuel Wallet
Start by connecting a Fuel wallet with FUEL tokens. This includes both non-native EVM and SVM wallets.


Stake
From left to right, you can view your FUEL token balance in your Fuel wallet, your staked FUEL token balance on the shared sequencer network, and your accumulated rewards.
Press the "Stake" button next to your balance to begin the staking process.

Enter the number of tokens you wish to delegate to the validators. Then, click the "Deposit" button to stake your tokens.
Please note: unlike staking on mainnet Ethereum, the validator delegation process is automatically assigned, so you don’t need to manage each validator manually.

After successfully depositing, your total staked token balance will update. The dashboard below will display the distribution of your delegated tokens. In the example diagram, the tokens are distributed evenly among the validators.

Withdraw
Withdrawing from the shared sequencer network is as simple as staking. Click the "Withdraw" button beside the amount of tokens you have staked.

Select the amount of FUEL tokens you wish to withdraw, then click the "Withdraw" button.

Your undelegated token balance will return to your Fuel wallet, and the validator dashboard will update accordingly as well.

Claim Rewards
Once you have staked your FUEL tokens, you will start to accumulate FUEL rewards. Click "Claim" next to your earned rewards balance to collect your tokens.

How to Stake on Mainnet Ethereum
Please visit app.fuel.network/staking/on-ethereum to stake your tokens on the Fuel Shared Sequencer Network from Ethereum. Below is a help guide:
Connect EVM Wallet
Start by connecting an Ethereum wallet with FUEL tokens, whether they are already in the shared sequencer network or in your mainnet wallet.

Stake
From left to right, you can see your balance of FUEL tokens in your Ethereum wallet, your balance of FUEL tokens in the shared sequencer network, and the rewards accumulated.
Please note that tokens still in your mainnet Ethereum wallet must be migrated to the shared sequencer network first before you can delegate and stake. Don’t worry, as this will follow the same flow.

Select Validator
Scroll down to the bottom of the page. There, you will see a list of validators. From the list, choose the validator to which you want to stake and delegate your FUEL tokens. Click the "Deposit" button.

Delegating Tokens
Select the amount of FUEL tokens you want to delegate, and approve those tokens.

Once approved, confirm your delegation.

After the tokens have been successfully delegated and staked, you will see your tokens under "Delegated Positions."

Claim Rewards
After staking your FUEL tokens, you will immediately start to accumulate FUEL rewards, which you can claim. Click "Claim" on any of your open delegated positions to collect your rewards.

Your rewards will be added to your balance in the Fuel shared sequencer network.

Withdrawal
You can undelegate and withdraw your staked FUEL tokens at any time. However, there is a standard 14-day unbonding period before the undelegation is completed, ensuring economic security within the PoS shared sequencer network and the Ethereum mainnet. After the 14-day period, the withdrawal process requires 4096 sequencer blocks approximately seven more hours before your tokens become available in your mainnet Ethereum wallet.
Undelegate
To undelegate your open positions, simply press the hamburger button beside your position and click the "Undelegate" button.

Here, you can select how many delegated tokens you wish to undelegate. These tokens will return to your balance in the shared sequencer network.

Withdraw Balance in Sequencer
Lastly, you can withdraw your tokens from the shared sequencer network back to your mainnet Ethereum wallet by clicking the "Withdraw" button beside your balance in the shared sequencer.

Here you can specify how many tokens you want to withdraw.

Chapter 1 - Why Fuel?
- 1.1 - Beginnings
- 1.2 - The Problem
- 1.3 - The Fuel Way
- 1.4 - So, What is Fuel?
- 1.5 - Building on Fuel: An Overview
Beginnings
A multitude of fragmented solutions—Layer 1s, rollups, DA layers, sequencers—clutter the blockchain landscape today, each striving to scale, decentralize, and power the internet's future. However, execution inefficiencies, unsustainable growth, and security compromises hamper the current array of solutions.
In the race to market, numerous solutions opted to copy and paste existing architectures. Few dared to build from the ground up. Why? Most projects focused on sustaining innovations, making incremental improvements to older frameworks. We took the opposite approach and aimed for disruptive innovation, challenging the status quo with a completely new architectural vision to overcome the limitations others had accepted.
Picture a group of early Ethereum developers, driven by a vision to enhance the performance, sustainability, interoperability, and developer experience of Ethereum. Recognizing the need for a fresh architectural approach to achieve ambitious goals, these devs envisioned a system incorporating years of blockchain evolution while adhering to cypherpunk ideals of decentralization and accessibility.
The blockchain ecosystem has undergone a remarkable transformation since the inception of Bitcoin in 2009. Bitcoin and Ethereum, as pioneering Layer 1 blockchains, established the foundation for decentralized systems, but quickly encountered scalability challenges. In response, alternative L1s offered differing approaches to decentralization, security, and performance. As demand for scalability grew, Ethereum explored modular approaches and various solutions like state channels, plasma, and eventually rollups—Layer 2 solutions that aggregate transactions to improve throughput while leveraging the security of L1s.
Sequencers, integral to rollups, emerged to manage transaction ordering and boost efficiency, forming a critical piece in the evolving blockchain landscape. This wave of innovation also sparked developments in Proposer-Builder Separation (PBS) and other modular solutions that allowed for specialization at various layers of the blockchain stack—execution, settlement, data availability, and consensus—pushing the boundaries of what these networks could achieve.
Despite such advancements, the blockchain landscape still lacks a crucial piece of the puzzle: scalability without compromise. Most solutions sacrifice decentralization for performance, or security for speed, resulting in trade-offs that undermine the core principles of blockchain technology.
Many L1 and L2 solutions boost transaction capacity by increasing node requirements, thus enhancing throughput and cutting latency. This approach, however, shrinks the pool of participants capable of validating and securing the chain.
Similarly, some rollups and sidechains achieve higher speeds by implementing trust assumptions that deviate from the foundational principles of security and decentralization. These solutions may rely on multi-signature schemes or other trust-based models to validate transactions, which introduce vulnerabilities. Users must place their trust in small groups of signers, which can be susceptible to hacking or coordination attacks.
This critical need for a scalable, trustless, and performant system—one that doesn’t trade off on the core principles of blockchain—remains unmet.
We built Fuel to address this critical gap.
In December 2020, Fuel emerged as the first optimistic rollup on Ethereum, with the launch of Fuel V1. We sought to create a trust-minimized sidechain that would inherit the security of Ethereum while introducing a radically redesigned execution model based on UTXOs, or Unspent Transaction Outputs.
Fuel V1 garnered significant attention within the blockchain community from day one. Many regarded Fuel V1 as the one “pure” rollup, primarily due to its approach to security and execution. Unlike other architectures, Fuel V1 demonstrated security inheritance without relying on third-party multi-signatures or sacrificing the integrity of optimistic fraud proofs.
Vitalik's appreciation for Fuel.
Fuel V1’s design philosophy set the bar for Ethereum rollups and ultimately, our vision, leading to a more refined architecture in Fuel V2.
Over the past three and a half years, Fuel has evolved significantly, morphing into a new blockchain architecture that thoughtfully addresses the common challenges faced by modern blockchains. Our vision culminated in what we now call Fuel V2, an operating system for rollups—the "Rollup OS." This framework empowers developers to build and customize their own rollups while leveraging the security and robustness of underlying L1s like Ethereum for settlement and access to Ethereum’s vast liquidity and assets.
Imagine Fuel as a robust framework designed to foster the development of sustainable and high-performance rollups, along with novel, advanced applications never before seen in blockchain. By providing this architecture, we empower developers to build innovative, decentralized solutions that push the boundaries of what's possible in the ecosystem.
We envision every application eventually evolving into its own app-chain, with Fuel providing the optimal architecture, tools, and developer experience for that future. Our commitment extends to creating pathways for the community to support millions of innovative app-chains, establishing Fuel as the foundation for the next generation of decentralized applications.
Fuel's narrative interweaves with Bitcoin and Ethereum's histories. Bitcoin's concise yet revolutionary whitepaper sparked a philosophical movement centered on self-sovereignty and cryptographic trust. Ethereum then expanded the horizon, introducing a programmable platform that unleashed developers' creativity and innovation.
Fuel acknowledges these contributions while seeking to fill the gaps left by conventional architectures. As we delve deeper into the intricacies of blockchain technology, we invite you to explore the problems we aim to solve and the vision we aspire to realize. Welcome to the beginnings of Fuel—a journey toward a sustainable, performant, and decentralized future.
The Problem
The blockchain landscape has advanced rapidly, but key issues still limit decentralized technology’s potential. Fuel is designed to address these fundamental challenges head-on. To understand its significance, we must first examine the core problems that blockchains face today.
The Performance Bottleneck
Performance can be measured in various ways, such as speed to finality, total execution load capacity, transactions per second and cost-efficiency. However, traditional networks like Ethereum struggle to scale efficiently across these aspects.
One of the critical issues is load. Ethereum's computation overhead limits its processing capacity, restricting the number of transactions per second (TPS), compute units per gas unit, and overall execution capacity. Proving cryptographic evidence of transactions consumes much of this computation. The EVM, for instance, bears a significant cryptographic burden as it verifies and updates the state tree after every transaction. Ethereum's execution layer compounds this inefficiency by poorly optimizing for the underlying hardware, creating unnecessary computational overhead.
Sequential execution of these computations further constraints throughput.If you remove the state tree and focus solely on EVM computation, Ethereum could potentially achieve up to 10,000 TPS. However, the real bottleneck comes from the execution side and the cryptographic evidence required for state verification. Fuel addresses this by removing the need for a state tree and enabling computations to run in parallel, dramatically increasing efficiency. Fuel not only makes execution more efficient but also allows it to scale horizontally, making the system far more accessible to users without driving up costs or limiting throughput.
Cost-efficiency remains a problem. Ethereum's transaction costs fluctuate often, spiking to inaccessible heights during congestion. This unpredictability harms both developers and users, constraining the types of applications they can scale.
The Scalability Dilemma
Many blockchain projects attempt to achieve scalability by increasing the hardware requirements for their nodes. This approach often makes it more expensive for users to participate, sidelining smaller users and compromising decentralization. Scalability demands more than incremental improvements. It requires fundamental changes to transaction and block processing that enhance efficiency without escalating the network's computational burden.
Early blockchains like Ethereum process transactions sequentially, executing one after another. This linear model severely limits performance on modern multi-core processors designed for parallel processing.
Parallelism, or the ability to process multiple transactions simultaneously, is one of the most promising solutions for blockchain scalability. However, enabling parallel transaction execution requires careful management of state access. If two transactions try to access the same state (for instance, attempting to spend the same funds), they can’t be processed in parallel. This leads to complex mechanisms in many blockchains that either attempt to predict state conflicts or reprocess conflicting transactions.
Fuel addresses these issues by adopting the UTXO model instead of Ethereum’s account-based model. In this model, every transaction defines its own state in the form of unspent transaction outputs, eliminating the risk of conflicting state access. As a result, Fuel can safely parallelize transaction execution, dramatically increasing throughput without compromising security. For developers, this complexity is abstracted away, allowing them to build seamlessly without needing to manage UTXOs directly.
Fuel's architecture introduces stateless primitives called predicates, enhancing efficiency and simplifying state management.This statelessness allows predicates to be trivially processed in parallel, enabling a high degree of concurrency in transaction execution. Predicates facilitate the execution of multiple operations within a single transaction while ensuring that conflicts with other transactions are avoided. Predicates don’t maintain state information between executions, enabling efficient parallel processing and significantly boosting throughput. Fuel's unique architecture enables critical performance gains, powering scalable real-world decentralized applications.
State Growth and Sustainability
Blockchain growth inflates state size. State encompasses a blockchain's stored data: account balances, smart contract bytecode, and dApp interactions. Unchecked state growth explodes exponentially, threatening system stability. Every new transaction adds more data, and this accumulation increases the burden on node operators. As the state becomes larger, nodes must store and manage increasingly extensive amounts of data, leading to higher hardware requirements and potentially threatening decentralization.
Ethereum is currently grappling with state bloat, which many core developers consider the network’s most pressing scaling issues. As the state grows, nodes must store increasingly large amounts of data, which increases hardware requirements. The ecosystem continually explores possible solutions, such as statelessness and state expiry, but has yet to fully implement any. Ethereum's backwards compatibility limits radical innovation, while Fuel's flexibility enables more flexibility and scalable solutions.
Fuel directly addresses state growth by minimizing unnecessary data accumulation. It discourages excessive state use with op code pricing, pushing developers to optimize their applications. By streamlining data storage and management, Fuel reduces the state nodes must maintain, easing the load on operators. Its architecture efficiently handles data, ensuring the state remains manageable as transactions are processed. Our approach preserves decentralization and accessibility, allowing the network to scale without encountering the challenges of unchecked state growth.
Interoperability and Fragmentation
Another major problem in today’s multi-chain world is interoperability. Ethereum's unified state machine succeeds largely by enabling application composition, universal asset access, and seamless dApp interactions. However, Ethereum's congestion has driven users to migrate to other L1s and L2s, fragmenting the ecosystem. Each new chain comes with its own set of challenges, including the need for separate wallets, token bridges, and onboarding processes.
Fuel is designed to reunite the fragmented ecosystem with a focus on interoperability at its core. Despite concerns about short-term fragmentation, Fuel's new VM and toolset aim to reduce ecosystem division long-term. Unlike most rollup projects, Fuel is not constrained by the EVM. Its flexible architecture enables innovative design choices for seamless interactions across multiple chains while maintaining full Ethereum compatibility. Fuel’s transaction model and block design simplify cross-chain integrations, making the movement of assets and data between chains easier to manage.
Fuel's proposed shared sequencer design prioritizes speed and efficiency, rapidly processing cross-chain transactions. Our fast sequencing empowers developers to build versatile, low-latency cross-chain applications, mitigating typical multi-chain fragmentation.
Fuel’s unique transaction and block architecture further enhances interoperability by providing execution evidence in the form of receipt roots and smart contract state roots. Verifiable proofs facilitate inter-chain interactions with Fuel, enhancing user experience and cross-chain fluidity.
The Future: A Modular, Decentralized World
Performance limitations, poor scalability, unsustainable state growth, and minimal interoperability plague the current blockchain landscape. These issues threaten blockchain decentralization and constrain thriving applications. Fuel's innovations—UTXO-based parallelism, modular architecture, and cross-chain capabilities—overcome these limitations, setting a new blockchain infrastructure standard.
The Fuel Way
Blockchains have largely followed Ethereum's evolutionary path since its launch.
Subsequent chains tout increased speed, scalability, power, and usability. They implement novel consensus mechanisms, databases, and ZK proving systems.
Despite these innovations, the core system remains largely unchanged: developers craft smart contracts for applications and assets (typically in Solidity or Rust). Users rely on centralized servers to read on-chain data and interact by signing messages with a standard private key, then routing these signed messages back through the same centralized servers.
Fuel charts a new course for the blockchain industry, prioritizing decentralization at its core. We're not just iterating; we're rebuilding blockchain architecture from the ground up.
Decentralized… Sustainably Decentralized
Blockchains fundamentally consist of a network of distributed nodes, all validating new blocks and transactions. The ability for independent, distributed and unqualified actors to participate in this process is what gives blockchains their valuable properties of liveness, censorship resistance and verifiability.
Bitcoin continues to take the most principled stance on maintaining these properties. The low node requirements and low bandwidth usage mean that Bitcoin full nodes can be run on devices as light as Raspberry Pis, and in locations as remote as outer space.
However, subsequent blockchains have all made ongoing compromises. Most newer blockchains today (including most layer-2s) can only be run on high-powered servers with data-center connections. And some high throughput projects remove the key cryptographic primitives of verifiability, such as the merkelization of state elements.
Fuel aims to pull the blockchain space back from this creeping centralization, back towards the values of Bitcoin. The Fuel architecture allows for high performance, while still running on consumer hardware. Fuel always maintains the property of cryptographic verifiability, allowing users to check the state of the chain without trusting third parties.
Blockchains are not Computers
Advancing blockchain technology demands more than incremental upgrades. True innovation often requires revolutionary action– including breaking changes. Fuel envisions revolutionizing both blockchain architecture and application development to unlock the technology's full potential.Traditional smart-contract platforms mimic computer systems, with blockchains serving as hardware and smart contracts as software. These contracts execute read and write operations, storing data to the chain's state—effectively treating it as a global Postgres database.
Fuel believes that blockchains are not simply scaled-up abstract mainframes but a different kind of computer—"trust machines." These machines are still programmable, but they operate under vastly different constraints than traditional execution environments. The role of a blockchain node is not to act as a cloud server but to verify the current state of the chain and all future state transitions with trustless integrity.
Moving computation off blockchain full nodes and shifting data outside of the blockchain’s state keep full node requirements low, allowing blockchains to scale without centralizing. Fuel enables developers to build smart applications without smart contracts, simplifying development while maintaining the decentralized ethos of blockchain technology.
ZK Pragmatism
Zero-knowledge technology has captured the imagination of researchers and developers from across the blockchain industry. The promise of succinct verification for arbitrary computation has opened up a whole new range of possibilities for scaling blockchains, making them verifiable, interoperable, and more. The thesis of building the future of ZK-powered blockchain tech has driven some of the most anticipated and well-funded projects in this space.
Fuel adopts a pragmatic approach to zero-knowledge (ZK) technology while recognizing its groundbreaking potential within and beyond blockchains. We share the industry's excitement about these new primitives and are actively integrating ZK technology into the Fuel stack (such as in Fuel’s hybrid-proving model and with the service chain’s ZK-powered bridge).
Fuel asserts that blockchain security, high performance, and interoperability should not hinge on ZK technology alone. Fuel pioneered the first optimistic rollup on Ethereum, diverging from the prevalent focus on ZK rollups among Ethereum scaling solutions. Fuel maintains that full ZK-verification cannot sustainably meet the market's stringent cost and performance demands.Proof generation costs and time constraints render fully ZK-proven chains incompatible with both cost-effectiveness and high-speed operations. Sustainable proofs and 'real-time proving' typically rely on ZK-specific hardware, which faces numerous production-readiness hurdles.
Fuel crafts cutting-edge blockchain technology, selectively integrating off-the-shelf ZK solutions to enhance its stack. The rise of generalized ZK-VMs like RISC Zero and Succinct’s SP-1 point to a future where ZK technology is commodified and easily available without the need for directly handling the necessary cryptography.
So What is Fuel?
Fuel is a next-generation execution layer for Ethereum, designed to offer unparalleled speed, flexibility, and scalability for both developers and users. But what exactly sets Fuel apart from other solutions? And why should you, as a developer, invest your time and energy into learning this architecture?
Fuel embodies a core philosophy of modularity and performance. For developers, Fuel’s appeal lies in both its design philosophy and the tools it offers. Here are a few key reasons why developers should pay attention to Fuel:
-
Unmatched Parallelization: Fuel’s unmatched parallelization enables simultaneous transaction processing, allowing significantly higher throughput than many other blockchains. By eliminating serial processing bottlenecks, developers can build scalable, efficient dApps without sacrificing performance. What truly sets Fuel apart is the introduction of predicates, stateless smart accounts that allow transactions to execute in parallel without conflict—something other blockchains struggle to achieve. Combined with the UTXO (Unspent Transaction Output) model, this ensures seamless, concurrent transaction execution, driving scalability to new heights.
-
Native Assets: Fuel natively supports a wide variety of assets, but its unique architecture handles them more efficiently. Unlike blockchains that handle only one native asset (like Ethereum's focus on ETH), Fuel enshrines all assets at the protocol level. This means developers don't need to create custom smart contracts for simple asset operations like transfers or balance checks. These operations are built into the system, significantly reducing complexity, time, and the risk of introducing vulnerabilities. Native assets also benefit from performance optimizations, avoiding the overhead of virtual machine processing.
-
Security and Safety: Fuel’s architecture eliminates many of the common vulnerabilities seen in smart contract platforms, such as reentrancy attacks. By integrating asset logic into the protocol itself, developers no longer have to rely on third-party contracts or code that could introduce risks. When you transfer tokens or execute other asset-related functions on Fuel, you do so with the assurance that the execution won’t be hijacked by malicious code, significantly enhancing security.
-
Developer-Friendly Tooling: Fuel offers an integrated, developer-friendly environment that makes building on its platform easier. Whether you're a seasoned blockchain developer or new to the space, Fuel provides a robust set of tools to support your development process. The Sway programming language, specifically designed for FuelVM, ensures that you’re writing optimized and secure smart contracts. In addition, Fuel’s native development kits (like Forc, the Fuel SDK, and Wallet SDK) make it easy to deploy, test, and manage decentralized applications.
-
Future-Proof Design: The blockchain space evolves rapidly, but Fuel is built to grow alongside it. Its modular design allows for the easy adoption of future innovations, whether in virtual machine improvements, consensus upgrades, data availability solutions or ZK. This flexibility ensures that Fuel can adapt to whatever comes next without developers having to constantly rework their applications.
Building on Fuel: An Overview
Building on Fuel empowers developers to create high-performance, scalable decentralized applications with cutting-edge tools and infrastructure. Fuel’s architecture prioritizes speed, security, and developer productivity. This section outlines the core components of the Fuel ecosystem. We will explore each component in further detail in Part 2.
The FuelVM (Fuel Virtual Machine)
The FuelVM incorporates years of blockchain design to bring the Ethereum community a reliable machine architecture built for longevity. It drives the Fuel Network and delivers exceptional performance by processing transactions in parallel. Unlike most blockchain virtual machines like the Ethereum Virtual Machine (EVM), which execute transactions serially, FuelVM handles concurrent processing, dramatically increasing throughput.
The FuelVM draws on a variety of architectures, including RISC-V, ARM ISAs, Bitcoin scripts, and the Ethereum Virtual Machine, to create a low-level execution environment optimized for blockchain use cases. By offering state-minimized facilities like native assets, ephemeral scripting, and spending conditions, it reduces the load on full nodes, improving network sustainability. Developers can avoid the inefficiencies of traditional state-heavy designs and build applications that deliver high performance while keeping the network decentralized and accessible.
As of May 2024, the FuelVM can achieve asset transfer benchmarks of 21,000 transactions per second (TPS) per core on high-end CPUs, offering unparalleled speed for modern blockchain applications.
The Fuel Transaction Architecture
Fuel’s transaction architecture brings together lessons from Bitcoin, Ethereum, Cosmos, and Solana to create a highly parallel and efficient transaction model. By using a UTXO (Unspent Transaction Output) model, Fuel enables parallel execution both within and across blocks, allowing developers to process transactions quickly without overloading the network.
Fuel transactions are flexible enough to handle everything from simple asset transfers to complex multi-party, multi-asset interactions and batch smart-contract calls. Developers can build sophisticated applications using advanced conditional logic with predicates, reducing the need for state-heavy smart contracts. By minimizing reliance on state, developers can ensure that applications perform efficiently without overburdening network resources.
Fuel’s transaction model also solves concurrency issues seen in other UTXO-based blockchains. This maintains a familiar developer experience for those coming from Ethereum while benefiting from the performance advantages of UTXO-based execution.
Fuel Ignition (Rollup)
Fuel Ignition will be the first Fuel V2 rollup to go live on Ethereum Mainnet. It aims to surpass traditional EVM rollups by delivering a vastly improved execution design. Initially starting as a more trustful Layer-2, Ignition’s ultimate goal is to evolve into a fully Ethereum-secured rollup with fraud proving, decentralized sequencing, and secure upgrades via a delayed multi-signature process.
Ignition’s focus on leveraging Ethereum’s security ensures that developers can build high-performance applications while benefiting from the strong security guarantees Ethereum offers. As Ignition develops, it will incorporate decentralized sequencing and Ethereum-based data availability (DA), further enhancing its trustless, scalable design.
The Fuel Network
Fuel operates as a network of interconnected rollups, designed to offer seamless interaction between different blockchains and rollups. Fuel rollups diverge from the copy-paste approach common in many rollup networks. Fuel's customizable VM configurations enable tailoring each network blockchain to developers' specific needs, enhancing adaptability across diverse use cases. Combined with its decentralized block production model, enabled by a shared sequencing and builder network, Fuel provides a fair and efficient system for managing transaction inclusion and interoperation between rollups.
Developer Tooling
The Fuel project realized early on the importance of thoughtful and considerate developer tooling. We consider developer time one of our community's most important assets and aim to optimize it for building high-value code. To maximize developer productivity and enable the creation of future-proof applications, we created our own suite of tools. These tools streamline building, testing, and deploying decentralized applications, freeing developers to focus on innovation.
Sway: Sway is a domain specific language (DSL) for modern blockchain programming which has familiar syntax, grammar and design ideology to Rust while incorporating blockchain specific functionality such as smart contract interface concepts. Sway is inherently designed to save developers time by providing a single programming language for constructing all critical blockchain application components such as: predicates, scripts, smart contracts, libraries, testing, deployment scripting, indexing and more.
Why not Rust or Solidity? Rust, primarily designed as a systems language, heavily bonds to the Low Level Virtual Machine (LLVM) toolchain and lacks focus on the special considerations of blockchain development. Solidity, a powerful language for developing on the Ethereum Virtual Machine, has many known shortcomings. Sway aims to combine the best aspects of both languages, offering developers a familiar yet powerful tool for blockchain development.
Other tools include:
-
Forc (Fuel Orchestrator): This command-line toolchain serves as the backbone of Fuel development. It supports everything from compiling Sway smart contracts to managing dependencies and deploying applications. Forc simplifies the entire development process, ensuring that developers can build robust dApps with ease.
-
Fuel Rust SDK: The Rust SDK allows developers to interact with Fuel’s blockchain using the Rust programming language. It offers a seamless experience for creating system-level applications and managing interactions with the Fuel Network.
-
Fuel Wallet SDK: The Fuel Wallet SDK provides developers with the tools to create secure, user-friendly wallets that natively interact with the Fuel ecosystem. It ensures developers can easily build wallets that integrate into decentralized applications.
-
Fuel Typescript SDK: The Typescript SDK allows developers to integrate Fuel into web applications, simplifying interaction with the Fuel blockchain and enabling frontend developers to build decentralized applications that connect with Fuel’s infrastructure.
Chapter 2 - The Architecture
- 2.1 - The FuelVM
- 2.2 - Transactions on Fuel
- 2.3 - Fuel Blocks
- 2.4 - Block Building in Fuel
- 2.5 - Fuel and Ethereum
- 2.6 - Security on Fuel
- 2.7 - Fees on Fuel
The FuelVM
Highlights:
-
The FuelVM serves as the core of the Fuel stack, informed by insights from various virtual machine designs like the EVM and Solana's SVM.
-
It supports state-minimized application development through features such as native assets and ephemeral scripting, promoting decentralized and accessible architecture.
-
The UTXO model facilitates parallelized transaction execution, enhancing throughput and reducing latency by allowing concurrent processing of non-conflicting transactions. The FuelVM is a register-based virtual machine, providing better performance compared to traditional stack-based designs and offering a structured instruction set for efficient operations.
-
Predicates, scripts, and contracts are essential components of the FuelVM, enabling flexible spending conditions, transaction processing, and state management within its execution environments.
The FuelVM lies at the core of the whole Fuel stack; it was created by taking into account years of learning from other virtual machine designs, like the EVM, Solana’s SVM and more.
The FuelVM enables developers to move away from stateful application designs often enabled by smart contracts by providing more feature-rich state-minimized facilities such as native assets, ephemeral scripting, and ephemeral spending conditions. By offering alternative methods for developers to build state-minimized applications, we enhance full node sustainability while maintaining a decentralized and accessible architecture, aligning with Ethereum’s core values.
In the following sections, we will discuss the key features of the FuelVM in detail.
UTXO Model and Parallelization
Fuel’s parallelized transaction execution model serves as a cornerstone for its efficiency and scalability. Parallelization dramatically improves throughput and reduces latency compared to traditional sequential processing methods. It breaks tasks into smaller sub-tasks, executing them simultaneously across multiple processing units.
Parallelization is built upon a foundation of Access Lists and the UTXO (Unspent Transaction Output) model, which works in tandem to enable concurrent processing of non-conflicting transactions.
Our tech leverages the UTXO model for performing transactions on Fuel. Transactions modeled through UTXOs handle everything from token transfers to smart contract calls.
Addresses on Fuel own unspent coins, allowing them to spend and perform transactions through the FuelVM.

Using the UTXO model helps achieve transaction parallelization. At runtime, users provide the inputs and outputs for their transaction. Transactions without overlap process in parallel, enabling Fuel to scale horizontally with the number of cores per machine.
Register based design
The FuelVM operates as a register-based virtual machine, unlike the EVM and many others, which use a stack-based architecture.
Register-based virtual machines consistently outperform stack-based virtual machines.
The FuelVM includes 64 registers, each 8 bytes, with 16 reserved and 6-bit addressable.
| value | register | name | description |
|---|---|---|---|
| 0x00 | $zero | zero | Contains zero (0), for convenience. |
| 0x01 | $one | one | Contains one (1), for convenience. |
| 0x02 | $of | overflow | Contains overflow/underflow of addition, subtraction, and multiplication. |
| 0x03 | $pc | program counter | The program counter. Memory address of the current instruction. |
| 0x04 | $ssp | stack start pointer | Memory address of bottom of current writable stack area. |
| 0x05 | $sp | stack pointer | Memory address on top of current writable stack area (points to free memory). |
| 0x06 | $fp | frame pointer | Memory address of beginning of current call frame. |
| 0x07 | $hp | heap pointer | Memory address below the current bottom of the heap (points to used/OOB memory). |
| 0x08 | $err | error | Error codes for particular operations. |
| 0x09 | $ggas | global gas | Remaining gas globally. |
| 0x0A | $cgas | context gas | Remaining gas in the context. |
| 0x0B | $bal | balance | Received balance for this context. |
| 0x0C | $is | instructions start | Pointer to the start of the currently-executing code. |
| 0x0D | $ret | return value | Return value or pointer. |
| 0x0E | $retl | return length | Return value length in bytes. |
| 0x0F | $flag | flags | Flags register. |
The FuelVM Instruction Set
The FuelVM instructions are 4 bytes wide and have the following structure:
- Opcode: 8 bits
- Register Identifier: 6 bits
- Immediate value: 12, 18, or 24 bits, depending on the operation.
The FuelVM instruction set has been documented in detail here: https://docs.fuel.network/docs/specs/fuel-vm/instruction-set.
Memory
FuelVM uses byte-indexed memory, configurable with the VM_MAX_RAM parameter. Hence, each instance of FuelVM can decide how much memory it wants to allocate for the VM.
Memory follows a stack and heap model. The stack begins from the left, immediately after the initialized VM data and call frame in a call context, while the heap starts at the byte indexed by VM_MAX_RAM.
Each byte allocation on the Stack increases the stack index by 1, and each byte allocation on the heap decreases its writable index by 1. Hence, the stack grows upwards, and the heap grows downwards.

The stack and the heap have the following essential registers associated with them:
$ssp( 0x05 ): Memory address of bottom of the current writable stack area.$sp( 0x06 ): Memory address on top of current writable stack area (points to free memory).$hp( 0x07 ): Memory address below the current bottom of the heap (points to used/OOB memory).
The FuelVM has ownership checks to ensure that contexts have a defined sense of ownership over particular regions in the memory and can only access memory from the region they own. We will elaborate more on this topic in later sections.
Predicates, Scripts and Contracts
Understanding further concepts for Fuel requires grasping:
- Predicates
- Scripts
- Contracts
Let’s dive deeper.
Predicates
Predicates are stateless programs that define spending conditions for native assets. Native assets go to predicates, and to determine whether they are spendable in a transaction, the FuelVM executes the bytecode and checks the boolean return value. If the returned value is true, the asset can be spent; if the returned value is false, then the transaction is invalid!
People can program various spending conditions, such as spending only if three out of five approve a transaction or if a transaction includes specific desired inputs and outputs, commonly known as intents.
Predicates operate statelessly, without persistent storage, and cannot call other smart contracts.
Scripts
Scripts serve as the entry point for Fuel transactions, dictating the flow of the transaction. Like predicates, they lack persistent storage. However, they can call contract Inputs, which are part of the Fuel transaction and can have persistent storage of their own.
This enables Fuel to natively support advanced features such as multi-calls, conditional contract execution, and more.
Contracts
Fuel provides support for smart contracts in its UTXO model. Smart contracts are stateful and can be called by other contracts. In Fuel, smart contracts are represented by the InputContract type. To learn more, refer to the section on InputContract.
The first call to a contract in a transaction occurs through a script, after which the contract can call other contracts.
Contracts have persistent storage, a key-value pair of 32-byte and 32-byte values. Various data structures are being considered to determine the optimal approach for committing to contract storage.
Contexts
A context is a way to isolate the execution of various execution environments for predicate estimation and verification, scripts, and contracts. Each context has its memory ownership, which we will expand on later.
There are four types of contexts in Fuel:
- Predicate Estimation
- Predicate Verification
- Script Execution
- Calls
The first three are called External contexts, as the $fp is zero, while Calls are called Internal contexts and will have a non-zero value for $fp.
Predicate Estimation
Fuel transactions provide predicateGasUsed for each predicate used. During verification, if predicateGasUsed is less than the total gas consumed during verification, the transaction reverts.

The user performs Predictive Estimation locally or by calling a remote full node, which executes the predicate in the FuelVM and returns the total gas consumed.
Predicate estimation context cannot do persistent storage or make calls to Contracts.
Predicate Verification
All predicate parts of the transaction are verified to return true before executing the transaction script. The FuelVM is used in the Predicate Verification context when verifying a transaction's predicates.

Predicate verification context cannot do persistent storage or make calls to contracts.
Script Execution
After verifying all predicates, the transaction script is executed; the script execution context cannot do persistent storage but can make calls to contract.

Calls
Call contexts execute contracts, offering flexibility, storing data persistently, and making contract calls.
Call context is created by either:
- Script calling a smart contract
- Contract calling a contract input

Each call creates a “Call Frame”, which is pushed to the Stack. A call frame holds metadata on the stack, aiding the execution of the call context in the FuelVM. A call context cannot mutate the caller's state and only access its stack and heap.
| bytes | type | value | description |
|---|---|---|---|
| Unwritable area begins. | |||
| 32 | byte[32] | to | Contract ID for this call. |
| 32 | byte[32] | asset_id | asset ID of forwarded coins. |
| 8*64 | byte[8][64] | regs | Saved registers from previous context. |
| 8 | uint64 | codesize | Code size in bytes, padded to the next word boundary. |
| 8 | byte[8] | param1 | First parameter. |
| 8 | byte[8] | param2 | Second parameter. |
| 1* | byte[] | code | Zero-padded to 8-byte alignment, but individual instructions are not aligned. |
| Unwritable area ends. | |||
| * | Call frame's stack. |
After a call context has successfully ended, its call frame is popped from the stack. However, any space allocated on the heap during the execution of the call context persists in memory.
A call context returns its value using the $ret and $retl registers. Large return values can be written to the heap and later read from the caller contexts.
Memory Policies
After understanding the various contexts for executing the FuelVM, we now discuss the policies for reading and writing to memory in contexts.
Read Policies for Context
A context can read any data from the stack in the range from the byte at index 0 (i.e., from the start of the memory ) to the highest ever $sp and between the current $hp and VM_MAX_RAM (i.e., until the end of the memory ) of the previous context that created the current context.
Any attempt to read from the region between the highest ever $sp during the context execution and the current $hp will return an error.

What do we mean by the highest ever $sp?
Since the stack can be grown and shrunk in size, it is possible that during the execution of some context, the $sp went until, for example, index 1000, but then elements were popped out of the stack, and now the current $sp is 900. In this scenario, the highest ever $sp during the execution of this call context is 1000, and hence, the memory region until 1000 is readable for the stack!
Write Policies for Context
A given context can write to any region between its $ssp and current $hp; hence, that memory region can be allocated and used for writing data.
Before writing to this memory region, allocate the bytes first. In the case of a stack, this is done using CFE and CFEI opcodes, while in the case of the heap, it is done via an ALOC opcode.

Note: Remember that once a context completes, all values on the stack (i.e., the call frame and all values allocated on the stack during execution ) are wiped down. Still, heap allocation stays, and the following context can only write data below the $hp of the existing context.
VM Initialization & Configuration
Configuration
The VM can be configured by setting the following parameters:
| name | type | value | note |
|---|---|---|---|
| CONTRACT_MAX_SIZE | uint64 | Maximum contract size, in bytes. | |
| VM_MAX_RAM | uint64 | 2**26 | 64 MiB. |
| MESSAGE_MAX_DATA_SIZE | uint64 | Maximum size of message data, in bytes. |
VM Initialization
This section outlines the process during VM initialization for every VM run. To initialize the VM, the following pushes to the stack sequentially:
-
Transaction hash (byte[32], word-aligned), computed as defined here.
-
Base asset ID (byte[32], word-aligned)
-
MAX_INPUTS pairs of (asset_id: byte[32], balance: uint64), of:
-
For predicate estimation and predicate verification, zeroes.
-
For script execution, the free balance for each asset ID seen in the transaction's inputs is ordered in ascending order. If there are fewer than MAX_INPUTS asset IDs, the pair has a zero value.
-
-
Transaction length, in bytes (uint64, word-aligned).
The following registers are then initialized (without explicit initialization, all registers are initialized to zero):
-
$ssp = 32 + 32 + MAX_INPUTS*(32+8) + size(tx)): the writable stack area starts immediately after the serialized transaction in memory (see above). -
$sp = $ssp:writable stack area is empty to start. -
$hp = VM_MAX_RAM:the heap area begins at the top and is empty to start.
Further Readings
- Nick Dodson’s tweet on what makes FuelVM unique: https://x.com/IAmNickDodson/status/1542516357886988288
- Blockchain Capital’s blog on FuelVM and Sway: https://medium.com/blockchain-capital-blog/exploring-the-fuelvm-86cf9ccdc159
- UTXO Model by River.com: https://river.com/learn/bitcoins-utxo-model/
Transactions on Fuel
Highlights:
-
Fuel utilizes the UTXO model for transactions, a method famously employed in the Bitcoin protocol. This method allows for advantages like parallel transaction execution. In this model, addresses can own native assets and spend these coins through transactions.
-
There are five distinct categories of transactions in Fuel, classified based on their operations: Script, Create, Mint, Upgrade, and Upload. Categorization helps define the various functionalities that users can perform within the Fuel ecosystem.
-
Fuel transactions are composed of several key components: Inputs, Scripts, Outputs, and Witnesses. Inputs consist of state elements that users access during the transaction and can include Coins, Contracts, and Messages.
-
The structure of a Fuel transaction allows for the inclusion of smart contracts as inputs, which can maintain persistent storage and can be utilized to execute complex operations beyond simple transactions, unlike the limitations faced by the Bitcoin protocol.
-
Witnesses play a crucial role in Fuel transactions by providing digital signatures and verification for spending coins. Block builders fill in these fields and exclude them from the transaction ID, allowing for flexible data handling in transaction processing.
Fuel uses the UTXO model for transactions on its blockchain. The model is popularly used in the Bitcoin protocol and has various advantages, including parallel transaction execution.
In Fuel, addresses can own native assets and spend coins with transactions. Fuel categorizes transactions into five types based on their blockchain operations:
- Script
- Create
- Mint
- Upgrade
- Upload

Fuel uses the UTXO model for transactions, introducing specific constructs we'll explore before examining the various transaction types:
- Inputs
- Script
- Outputs
- Witnesses
We'll explore Fuel transaction components in detail before examining individual transaction types.
Inputs
Fuel transactions use three types of Inputs, which are state elements accessed by users:
- Coins
- Contracts
- Messages
Coins
Coins are units for some asset that a user can spend as part of the transaction. Fuel natively supports multiple assets, unlike chains that only support one base asset (such as ETH for Ethereum). Asset creation is built into Fuel's protocol. For more details, see the native assets section in the appendix.
Users can own various denominations of certain assets in different numbers of Coins. For example, a Fuel address A can have a balance of some asset 100, with four coins of 25 denominations each, and some address B can have a balance of 100 for the same asset, but three coins of denomination 10, 40, 50.

An Input Coin has the following parameters attached:
| name | type | description |
|---|---|---|
| txID | byte[32] | Hash of transaction. |
| outputIndex | uint16 | Index of transaction output. |
| owner | byte[32] | Owning address or predicate root. |
| amount | uint64 | Amount of coins. |
| asset_id | byte[32] | Asset ID of the coins. |
| txPointer | TXPointer | Points to the TX whose output is being spent. |
| witnessIndex | uint16 | Index of witness that authorizes spending the coin. |
| predicateGasUsed | uint64 | Gas used by predicate. |
| predicateLength | uint64 | Length of predicate, in instructions. |
| predicateDataLength | uint64 | Length of predicate input data, in bytes. |
| predicate | byte[] | Predicate bytecode. |
| predicateData | byte[] | Predicate input data (parameters). |
The transaction invalidity rules for this input type can be seen here.
Contracts
A common question about the UTXO model concerns implementing smart contracts beyond ephemeral scripts.
Bitcoin's limited support for complex smart contracts stems from several core issues:
-
Bitcoin script is not Turing complete, meaning you cannot do things like loops inside Bitcoin
-
Bitcoin scripts in transactions lack persistent storage, limiting the blockchain's functionality.
Many incorrectly attribute Bitcoin's limitations to its UTXO model. However, these constraints stem from deliberate design choices. At Fuel, we embrace the UTXO model while supporting full Turing-complete smart contracts with persistent storage. We solve this problem by making stateful smart contracts an input for Fuel transactions.
Contracts have persistent storage and can own native assets. Users consume contracts by using the contracts as input for transactions. Then, users can call various external functions attached to contracts via the ephemeral script attached to the transaction.

| name | type | description |
|---|---|---|
| txID | byte[32] | Hash of transaction. |
| outputIndex | uint16 | Index of transaction output. |
| balanceRoot | byte[32] | Root of amount of coins owned by contract before transaction execution. |
| stateRoot | byte[32] | State root of contract before transaction execution. |
| txPointer | TXPointer | Points to the TX whose output is being spent. |
| contractID | byte[32] | Contract ID. |
When signing over contracts, txID, outputIndex, balanceRoot, stateRoot, and txPointer are initialized to zero values, which the block builder later fills in. This helps avoid concurrency issues with Contracts, as previously seen in the Cardano model.
When interacting with an AMM contract on Fuel, the process follows a specific flow. You begin by including the contract as an input to your transaction. Next, you call the external methods within an ephemeral script. Finally, you emit the contract as an output. This emitted contract can then be consumed as an input for the subsequent transaction involving this particular AMM contract. This approach allows for efficient state management and seamless interaction with the AMM on the Fuel platform.
The transaction invalidity rules for this input type can be seen here.
Messages
The Block Builder creates messages created as part of sending messages from the L1 to the L2. Messages make deposits to the Fuel rollups from the L1 possible, and we will discuss them in better detail later in the Fuel & Ethereum section.
NOTE: An Input Message can only be consumed as an Input as part of a transaction, and is then destroyed from the UTXO set.

A fuel InputMessage consists of the following parameters:
| name | type | description |
|---|---|---|
| sender | byte[32] | The address of the message sender. |
| recipient | byte[32] | The address or predicate root of the message recipient. |
| amount | uint64 | Amount of base asset coins sent with message. |
| nonce | byte[32] | The message nonce. |
| witnessIndex | uint16 | Index of witness that authorizes spending the coin. |
| predicateGasUsed | uint64 | Gas used by predicate execution. |
| dataLength | uint64 | Length of message data, in bytes. |
| predicateLength | uint64 | Length of predicate, in instructions. |
| predicateDataLength | uint64 | Length of predicate input data, in bytes. |
| data | byte[] | The message data. |
| predicate | byte[] | Predicate bytecode. |
| predicateData | byte[] | Predicate input data (parameters). |
The transaction invalidity rules for this input type can be seen here.
Scripts
Fuel scripts are ephemeral scripts that express the various actions taken during a transaction; a script can call the contracts provided as inputs or perform other arbitrary computation.
Fuel implements multi-call functionality through scripts, enabling efficient batch transactions. This approach allows users to:
-
Provide up to MAX_INPUTS contracts in a single transaction
-
Call external methods on these multiple contracts
As mentioned in the FuelVM section, the FuelVM is in the Script Context, scripts cannot have their own persistent storage.
Outputs
Fuel transactions have Outputs, which define the creation of new UTXOs post-transaction; these Outputs can then be inputs for the next set of transactions.
There are five types of possible Output types in a Fuel transaction:
- Coin
- Contract
- Change
- Variable
- ContractCreated
One thing to note is we have three Outputs dealing with Coins, and the table below summarizes the core differences (we will expand more in further sections):
| OutputCoin | OutputChange | OutputVariable | |
|---|---|---|---|
| Amount | Static | Automatically set | Set by script/contract |
| AssetID | Static | Static | Set by script/contract |
| To | Static | Static | Set by script/contract |
NOTE: A Coin Output (Coin, Change, Variable) with an amount of zero leads to the pruning of the output from the UTXO set, which means coin outputs of amount zero are not part of the UTXO set.
OutputCoin
Output Coins are new coins sent to a Fuel Address, which become spendable as Input Coins in further transactions.
| name | type | description |
|---|---|---|
| to | byte[32] | Receiving address or predicate root. |
| amount | uint64 | Amount of coins to send. |
| asset_id | byte[32] | Asset ID of coins. |

The transaction invalidity rules for this output type can be seen here.
OutputContract
OutputContracts are newly generated contract outputs that become available as InputContracts for a specific contract ID in subsequent transactions utilizing this contract as an Input. They contain the newly updated index, balanceRoot, and stateRoot of the contract after being processed as part of the transaction.
NOTE: Every InputContract part of the transaction must always have a corresponding Output Contract.

| name | type | description |
|---|---|---|
| inputIndex | uint16 | Index of input contract. |
| balanceRoot | byte[32] | Root of amount of coins owned by contract after transaction execution. |
| stateRoot | byte[32] | State root of contract after transaction execution. |
The transaction invalidity rules for this output type can be seen here.
OutputChange
An OutputChange, included as one of our outputs for a specific assetId, enables the recovery of any unused balance from the total input balance provided in the transaction for that assetId.
For example, an OutputChange can collect any ETH not spent as gas or any USDC not swapped as part of a DEX transaction.

NOTE: There can only be one OutputChange per asset_id in a transaction.
| name | type | description |
|---|---|---|
| to | byte[32] | Receiving address or predicate root. |
| amount | uint64 | Amount of coins to send. |
| asset_id | byte[32] | Asset ID of coins. |
The transaction invalidity rules for this output type can be seen here.
OutputVariable
OutputVariable acts as a placeholder for coins created in the execution of scripts and contracts since they can create a coin of an arbitrary amount and to an arbitrary user. This is useful in scenarios where the exact output amount and owner cannot be determined beforehand.
NOTE: This means every transaction using mint internally will need an OutputVariable for that particular assetID.

Consider a scenario where a contract transfers its output coin to a user only upon receiving a correct value. In this case, we can utilize a variable output at the end of the transaction. This output may or may not have a value attached to it, depending on whether the condition is met and have an arbitrary owner.
Variable Outputs are used via the TRO opcode.
| name | type | description |
|---|---|---|
| to | byte[32] | Receiving address or predicate root. |
| amount | uint64 | Amount of coins to send. |
| asset_id | byte[32] | Asset ID of coins. |
The transaction invalidity rules for this output type are available in our documentation.
OutputContractCreated
The OutputContractCreated output indicates that a new contract was created as part of the transaction. The parameters include the contractID and the initial state root for this contract.
| name | type | description |
|---|---|---|
| contractID | byte[32] | Contract ID. |
| stateRoot | byte[32] | Initial state root of contract. |
The transaction invalidity rules for this output type can be seen here.
Witness
The witness is a parameter attached to transactions. The block builders fill in witnesses and are not part of the transaction ID. A Witness is usually used to provide digital signatures for verification purposes, for example, the signature to prove the spending of a Coin or anything else.
Witnesses are not part of the transaction ID, which allows someone to sign over a transaction and provide it as part of the transaction.
NOTE: The protocol doesn't limit witnesses to providing signatures only; they serve to fill in any data and enable various interesting use cases, like State Rehydration.
Each witness contains a byte array data along with the field dataLength helping know the length of this data.
| name | type | description |
|---|---|---|
| dataLength | uint64 | Length of witness data, in bytes. |
| data | byte[] | Witness data. |
| asset_id | byte[32] | Asset ID of coins. |
Multiple witnesses can be provided as part of the transaction, and the inputs can indicate which witness block builders, contracts, scripts or predicates can look at to verify the validity of being able to spend the input by providing the index at which their witness lives.
TransactionScript
Script transactions are transactions that, as the name suggests, have Inputs, Outputs, and a Script that dictates what happens as part of the transaction.
Note: Scripts are optional in transactions of type TransactionScript. For example, a simple token transfer can work only on inputs and outputs, with no requirement for a script. Scripts are mainly leveraged when you want to do other things as part of your transaction beyond simply transferring or burning assets.
The transaction's script can compute arbitrary amounts and call other contracts. A famous example of script transactions is using an AMM or transferring a token.
| name | type | description |
|---|---|---|
| scriptGasLimit | uint64 | Gas limits the script execution. |
| receiptsRoot | byte[32] | Merkle root of receipts. |
| scriptLength | uint64 | Script length, in instructions. |
| scriptDataLength | uint64 | Length of script input data, in bytes. |
| policyTypes | uint32 | Bitfield of used policy types. |
| inputsCount | uint16 | Number of inputs. |
| outputsCount | uint16 | Number of outputs. |
| witnessesCount | uint16 | Number of witnesses. |
| script | byte[] | Script to execute. |
| scriptData | byte[] | Script input data (parameters). |
| policies | Policy [] | List of policies, sorted by PolicyType. |
| inputs | Input [] | List of inputs. |
| outputs | Output [] | List of outputs. |
| witnesses | Witness [] | List of witnesses. |
NOTE: Script transactions lack the ability to create contracts, therefore they cannot produce a ContractCreated output type. For additional transaction invalidity rules, refer to our documentation.
TransactionCreate
TransactionCreate is used to create new contracts; the parameters allow for contracts with initialized storage slots.
The contract ID of smart contracts on Fuel is calculated deterministically, and the calculation mechanism is referred to here.

| name | type | description |
|---|---|---|
| bytecodeWitnessIndex | uint16 | Witness index of contract bytecode to create. |
| salt | byte[32] | Salt. |
| storageSlotsCount | uint64 | Number of storage slots to initialize. |
| policyTypes | uint32 | Bitfield of used policy types. |
| inputsCount | uint16 | Number of inputs. |
| outputsCount | uint16 | Number of outputs. |
| witnessesCount | uint16 | Number of witnesses. |
| storageSlots | (byte[32], byte[32])[] | List of storage slots to initialize (key, value). |
| policies | Policy [] | List of policies. |
| inputs | Input [] | List of inputs. |
| outputs | Output [] | List of outputs. |
| witnesses | Witness [] | List of witnesses. |
The transaction invalidity rules for this transaction type can be seen here.
TransactionMint
The block producer uses this transaction to mint new assets. It doesn’t require a signature. The transaction is currently used to create the block producer's fees. The last transaction in the blocks is a Coinbase transaction, allowing the block producer to collect fees for building the block.

| name | type | description |
|---|---|---|
| txPointer | TXPointer | The location of the Mint transaction in the block. |
| inputContract | InputContract | The contract UTXO that assets are minted to. |
| outputContract | OutputContract | The contract UTXO that assets are being minted to. |
| mintAmount | uint64 | The amount of funds minted. |
| mintAssetId | byte[32] | The asset IDs corresponding to the minted amount. |
| gasPrice | uint64 | The gas price to be used in calculating all fees for transactions on block |
The transaction invalidity rules for this transaction type can be seen here.
TransactionUpgrade
The Fuel network employs consensus parameters, subject to occasional upgrades. The network's state transition function resides on-chain, allowing privileged addresses to upgrade it when necessary.
Therefore, at any given moment, a TransactionUpgrade might attempt to perform one of the following actions:
-
Trying to upgrade the consensus parameters
-
Trying to upgrade the state transition function

| name | type | description |
|---|---|---|
| upgradePurpose | UpgradePurpose | The purpose of the upgrade. |
| policyTypes | uint32 | Bitfield of used policy types. |
| inputsCount | uint16 | Number of inputs. |
| outputsCount | uint16 | Number of outputs. |
| witnessesCount | uint16 | Number of witnesses. |
| policies | Policy [] | List of policies. |
| inputs | Input [] | List of inputs. |
| outputs | Output [] | List of outputs. |
| witnesses | Witness [] | List of witnesses. |
The transaction invalidity rules for this transaction type can be seen here.
TransactionUpload
Before performing an upgrade, operators must upload the Fuel state transition bytecode to the chain. This requires uploading the bytecode via multiple transactions. TransactionUpload allows us to split the bytecode into multiple subsections and upload each subsection sequentially over multiple transactions.
On successful upload of all subsections, the transaction reaches completion, and the system adopts the new bytecode.

| name | type | description |
|---|---|---|
| root | byte[32] | The root of the Merkle tree is created over the bytecode. |
| witnessIndex | uint16 | The witness index of the subsection of the bytecode. |
| subsectionIndex | uint16 | The index of the subsection of the bytecode. |
| subsectionsNumber | uint16 | The total number of subsections on which bytecode was divided. |
| proofSetCount | uint16 | Number of Merkle nodes in the proof. |
| policyTypes | uint32 | Bitfield of used policy types. |
| inputsCount | uint16 | Number of inputs. |
| outputsCount | uint16 | Number of outputs. |
| witnessesCount | uint16 | Number of witnesses. |
| proofSet | byte[32][] | The proof set of Merkle nodes to verify the connection of the subsection to the root. |
| policies | Policy [] | List of policies. |
| inputs | Input [] | List of inputs. |
| outputs | Output [] | List of outputs. |
| witnesses | Witness [] | List of witnesses. |
The transaction invalidity rules for this transaction type can be seen here.
Appendix
Native Assets
In Fuel, apart from Eth (which is called the base asset), the functionality of minting and burning assets is enshrined in the protocol. The FuelVM provides op-codes for creating, minting, and burning assets, MINT and BURN, respectively.
All native assets can be spent using the same rules as the base asset, allowing Fuel developers and users to fully utilize the UTXO model and the resulting parallelization.
To explore Native assets further, it is recommended that you look at Sway Standards, which provide various standards ( like SRC-3, SRC-20, and many more ) related to native assets.
Fuel Blocks
Highlights:
-
In Fuel, blocks are constructed by Block Builders, who process both transactions and messages to create the blocks. Users send transactions either directly to the builder or through layer 1, while messages originate from layer 1. The Fuel and Ethereum section of the book provides further details on this process.
-
Each Fuel block begins with a header that consists of three main fields: the Application Header, the Consensus Header, and Block Header Metadata. This structure facilitates efficient management and processing of block-related information.
-
The Application Header records critical operational details for the Fuel rollup, comprising four essential components: the da_height, consensus_parameters_version, state_transition_bytecode_version, and generated fields. These components work together to ensure the rollup operates correctly and efficiently.
-
The Consensus Header tracks the hash of the Application Header, providing a secure and verifiable method for maintaining consensus within the Fuel network. This header is crucial for ensuring the integrity of the block.
-
Fuel blocks also include a Coinbase transaction, which enables block producers to collect fees for their work. This Mint transaction must be the last in the block and is capped at the total fees processed from all transactions within that block, ensuring a fair and controlled fee structure.
Blocks in Fuel are built by entities called Block Builders. Fuel blocks are made by processing transactions and messages. Transactions can be sent directly to the builder or via layer 1, while messages are sent from layer 1. In the Fuel and Ethereum section of the book, we expand further on messages and transactions sent from the L1.
Block Header
A Fuel block header at the top consists of three fields:
- Application Header
- Consensus Header
- Block Header Metadata
pub struct BlockHeaderV1 {
pub application: ApplicationHeader<GeneratedApplicationFields>,
pub consensus: ConsensusHeader<GeneratedConsensusFields>,
metadata: Option<BlockHeaderMetadata>,
}
Application Header
The application header records essential information regarding the operation of the Fuel rollup.
The application header at the high level consists of four essential components:
pub struct ApplicationHeader<Generated> {
pub da_height: DaBlockHeight,
pub consensus_parameters_version: ConsensusParametersVersion,
pub state_transition_bytecode_version: StateTransitionBytecodeVersion,
pub generated: Generated,
}
da_height
The da_height field records the latest block L1 block until the messages sent from the L1 → L2 have been processed; this is helpful later in fraud proving to establish that a particular message was sent from the L1 to the L2 rollup but wasn’t processed as part of the block that included the messages up to the block of which it was part.
consensus_parameters_version
The Fuel rollup has a set of upgradeable consensus parameters, which are upgradable via Transactions of type Upgrade. For each consensus parameter upgrade, a new version for consensus_paramters_version must be assigned, helping us track which set of consensus parameters we are using while building a particular block.
state_transition_bytecode_version
The Fuel rollups keep the WASM compiled bytecode of their state transition function as part of the chain facilitating forkless upgrades for the Fuel rollups.
The new state transition function is uploaded via the Upload transactions, while the upgrade is done via the Upgrade transactions. Each upgrade updates the state_transition_bytecode_version, and this version helps keep track of which state transition function is being used to process transactions for a given block.
generated
The section contains various rollup-specific fields around execution for a specific block. The Fuel flagship rollup has the following fields for generated:
pub struct GeneratedApplicationFields {
/// Number of transactions in this block.
pub transactions_count: u16,
/// Number of message receipts in this block.
pub message_receipt_count: u32,
/// Merkle root of transactions.
pub transactions_root: Bytes32,
/// Merkle root of message receipts in this block.
pub message_outbox_root: Bytes32,
/// Root hash of all imported events from L1
pub event_inbox_root: Bytes32,
}
Consensus Header
The consensus header is another top-level field for the Block Header for Fuel rollups, it is configurable and for the flagship Fuel rollup only keeps track of the hash of the Application Header.
pub struct GeneratedConsensusFields {
/// Hash of the application header.
pub application_hash: Bytes32,
}
Block Header Metadata
The Block Header Metadata is used to track metadata. The current flagship Fuel rollup includes a field tracking the block ID, which represents the hash of the block header.
pub struct BlockHeaderMetadata {
/// Hash of the header.
id: BlockId,
}
Coinbase Transaction
Fuel blocks contain a Coinbase transaction; block producers use Coinbase transactions to collect fees for building blocks. The Coinbase transaction is a Mint transaction, where the mintAmount cannot exceed the fees processed from all transactions in the block. The protocol also requires the Coinbase transaction to always be the last transaction in the block.
Block Building in Fuel
Highlights:
-
The Block Builder plays a pivotal role in block building within Fuel by processing messages and transactions, constructing blocks, and submitting them to Layer 1 while ensuring soft-finality on Layer 2.
-
The Fuel Block Builder uses a messaging system to facilitate the transfer of information between Layer 1 and Layer 2, enabling both regular transactions and forced transaction inclusion. This feature empowers users to bypass potential censorship by relaying their transactions directly from Layer 1.
-
To guarantee reliable processing of messages and transactions, the Block Builder appends a data height (da_height) to each block, linking it to a specific Layer 1 block. By utilizing Merkle trees to store commitments, the system ensures that all events are processed in a deterministic manner, allowing for transparent validation and slashing of the Block Builder if necessary.
-
The Block Builder also processes local transactions, allowing users to send transactions directly to it for more efficient batching and compression. This method reduces transaction costs and accelerates soft finality, providing users - with quicker confirmations compared to traditional Layer 1 submissions.
-
The Block Builder determines transaction ordering and manages miner extractable value (MEV) in Fuel. It prioritizes transactions based on the tips provided, contrasting with the first-in-first-out approach found in many other Layer 2 solutions, which optimizes user incentives within the network.
This section focuses on block building in Fuel and the role that the Block Builder plays in this process.
The Fuel Block Builder is a component in Fuel rollups, which is responsible for:
-
Processing messages from L1 → L2
-
Processing transactions in the mempool
-
Building blocks and submitting them to the Layer 1
-
Providing soft-finality on the Layer 2
L1 → L2 processing
Fuel rollups have a messaging system (from L1 → L2 and vice versa), which we will discuss further in the next section on bridging. In addition to relaying bridge messages, this system allows transactions to be sent directly from L1, which is used for forced transaction inclusion.
The Fuel Block Builder uses a relay to receive messages and transactions from L1 → L2, we will discuss both of these cases individually now.
L1 Messages
Block Builder receives relayed messages from Layer 1 emitted as L1 events. The message is then picked up as part of the block-building process; each message sent from the L1 has the following format:
| name | type | description |
|---|---|---|
| sender | bytes[32] | The identity of the sender of the message on the L1 |
| recipient | bytes[32] | The recipient of the message on the Fuel Blockchain |
| nonce | bytes[32] | Unique identifier of the message assigned by the L1 contract |
| amount | uint64 | The amount of the base asset transfer |
| data | byte[] | Arbitrary message data |
The block builder creates an output of type OutputMessage, and after creating this output, it completes the message processing.
Applications can leverage these OutputMessage(s) as they see fit. One example is the deposit process, where the bridge contract mints new ETH on the L2 after receiving specific messages that prove deposits on the L1 (we will discuss this further in the next section).
L1 Transactions and Forced Inclusion
Fuel provides forced inclusion of transactions. If a user feels the L2 block builder is attempting to censor, they can emit a serialized transaction from the L1 as an event, forcing the L2 block builder to include the transaction in the block building. This process, called “Forced Inclusion,” guarantees user censorship resistance.
The Fuel transactions sent from L1 are emitted as events via the L1 and have the following format:
| name | type | description |
|---|---|---|
| nonce | bytes[32] | Unique identifier of the transaction assigned by the L1 contract |
| max_gas | uint64 | The maximum amount of gas allowed to use on Fuel Blockchain |
| serialized_transaction | byte[] | The serialized transaction bytes following canonical serialization |
Forced Inclusion allows the processing of all transaction types except Mint, which can only be created by the Fuel Block Builder. This exception does not restrict security guarantees for users' censorship resistance.
Guarantees around L1 processing
How does the L2 guarantee it will always process messages or transactions sent from L1?
This is done by appending the da_height, i.e., the L1 block up to which the current block processes messages and transactions. A commitment for all the events and transactions is stored as part of the block header, using a Merkle tree and its root.
All events from L1 → L2 (both inbox messages and forced transactions), are ordered by their block number and the index in that block. Following this order allows us to find a deterministic way of creating this Merkle tree.
We create this Merkle tree and store the root in the event_inbox_root field as part of the block header.
Fuel blocks are subject to later challenges. If it's proven that a specific message or transaction was omitted or not processed, the responsible block builder can be penalized.
Processing Local Transactions
Apart from processing messages and transactions from L1 → L2, the Block Builder is responsible for processing transactions sent to it locally. Users can send transactions to the Block Builder locally, collected in its Mempool, and then processed and sent to Layer 1.
By using clever batching and compression techniques (gzip or zstd) this system offers users lower transaction costs compared to direct Layer 1 submissions.
Another advantage of sending transactions directly to the Block Builder is getting faster soft finality on the L2. For a transaction sent via L1 to be processed, the L1 block must be finalized first.
Block Building and Proposing
The Fuel Block Builder is required to bundle transactions into blocks and propose them to Layer 1 as part of processing transactions. Committed blocks on Fuel enter a 'Challenge Window' after commitment. Once this window closes, the block and its corresponding state are considered to have reached 'L1 finality'.
Fuel Block Builder currently sends the block hash and block height as new updates to the onchain message portal, along with blobs containing transactions and other data, to provide DA for that specific block.
Transaction Ordering and MEV
The current Fuel Block Builder decides the priority of a transaction by sorting with tip/max_gas, which means unlike many other L2s, the network isn’t FIFO (First In First Out); this also means that in Fuel, the Priority of your transaction inclusion is:
-
Directly proportional to the
tipyou provide as part of the transaction -
Inversely proportional to the
max_gasyou permit for your transaction
Soft Finality
The Block Builder also plays a major role in providing soft finality for L2 transactions. As an L2 participant, you can choose the level of finality at which you're comfortable making business decisions.
When the Block Builder orders and processes your transaction, it provides a soft finality. This can be considered confirmed unless the Block Builder fails to finalize it on L1.
Appendix
Full Nodes
The fuel-core software also allows you to run a Full Node. A Full Node collects the latest updates on Layer 2 from the peers and broadcasts incoming transactions to the network.
Full Nodes cannot build blocks; instead, they receive them as updates via p2p and re-execute them locally to maintain the correct state with complete verification.
By running the Full Node, you can, as a user, be given the ability to keep verifying the L2 yourself and, hence, also be able to send fraud proofs. You also get your own GraphQL endpoint, which can broadcast your transactions to the network.
All Fuel GraphQL providers run Full Nodes themselves to provide you with the latest Fuel state and allow you to broadcast transactions.
Fuel and Ethereum
Highlights:
-
Fuel Ignition leverages Ethereum as its Layer 1 for settlement and data availability, aligning with Ethereum's core values of sustainability, security, and accessibility for everyday users. This choice emphasizes Fuel's commitment to building a long-term, decentralized ecosystem.
-
By utilizing one of the most established and decentralized Layer 1 networks, Ethereum provides a robust foundation for Fuel’s rollup, ensuring reliable performance and security. Fuel's rollup inherits Ethereum's security model, which safeguards user funds and enables fraud-proof mechanisms directly on the Ethereum blockchain.
-
Fuel allows seamless messaging between Layer 1 and Layer 2, ensuring that any message sent to Ethereum must be processed on Fuel and vice versa. This capability enhances user experience and guarantees censorship resistance.
-
Users can easily deposit and withdraw ETH, transferring assets between Layer 1 and Layer 2. They can deposit ETH directly to Fuel and initiate withdrawals by burning tokens on Layer 2, with the system ensuring timely and secure processing.
-
Fuel actively pursues innovation through techniques like hybrid proving, optimizing the proving system by reducing complexity and shortening challenge windows. By embracing a modular tech stack, Fuel remains adaptable, exploring integration with alternative Layer 1s and data availability solutions to enhance its ecosystem.
Fuel Ignition uses Ethereum as a Layer 1. We chose Ethereum as Fuel’s L1 for both Settlement and Data availability of the L2, because we think Fuel shares many of Ethereum’s values:
-
Building for long-term sustainably
-
Building with an emphasis on security
-
Focus on consumer hardware and making participation in the protocol accessible for ordinary people
Ethereum is one of the most decentralized L2s. Ethereum has a long-standing presence and has focused on a rollup-centric roadmap for years. These factors make it the ideal foundation for building a rollup.

Inheriting Ethereum’s Security
Fuel’s flagship rollup, Ignition, inherits Ethereum’s security. The natural question from the previous statement is, what do we mean by inheriting Ethereum’s security?
Fuel uses Ethereum as the layer to keep users’ funds and propose its latest blocks and corresponding state updates. We deploy smart contracts that continuously get updates of Fuel Layer 2.
Then, we have fraud-proving performed directly on the Ethereum L1 to prove that something about the posted blocks or related state updates is wrong. We also allow permissionless messaging and transaction inclusion via the L1 to ensure the user doesn’t experience any censorship resistance.
This gives the user guarantees that as long as Ethereum is secure and the honest majority assumption for it is held:
- No fraud blocks or state updates can be sent
- Their funds are always safe on the Layer 1
- They can never be stopped from withdrawing them or being able to send any transaction to the L2 (forced inclusion)
Now, we will discuss each of the properties we described above.
Messaging
Fuel allows for messaging between L1 → L2 and L2 → L1, which means you can send any arbitrary message from Layer 1 to Layer 2 and vice versa. The protocol guarantees that if a message is included in the L1, it has to be processed on the L2 and vice versa. Let’s discuss both of these cases individually.
L1 → L2 Messaging
The Fuel Message Portal facilitates message processing from L1 -> L2. Its method, sendMessage, accepts the L2 recipient (a Fuel Address) and the corresponding message to be sent. After a successful call to this method, a MessageSent event is emitted on Layer 1.
As discussed in the section on block building, part of processing the Fuel blocks requires committing to some L1 block height, up to which the block builder processes messages and transactions, this forces the Block builder to include all messages from the L1 (as in case of failure, the builder can be slashed).

As part of processing the message blocks from the L1, the block builder looks at the event and mints an OutputMessage transaction to the particular Fuel address with the specific data.
L2 → L1 Messaging
Fuel also allows messages from the L2 -> L1 to be sent using MessageOut receipts. Every Fuel block includes a receipt root, the root of all receipts that were part of the block. This allows anyone to make a call to the relayMessage function of the Fuel Message Portal; a Merkle proof of inclusion is required to perform for the message you are trying to process along with that, it checks whether the block for which the message is being processed has been finalized or not (i.e., it outside of the challenge window).

Processing the message on the L1 coming from the L2 is done by calling the specific L1 address to which the message is sent to with some desired payload.
ETH Deposits and Withdrawals
A core part of using the Fuel rollup is depositing ETH from the L1 to Fuel and withdrawing it from the L2. We will discuss both of these scenarios individually.
ETH Deposits
The user can call the depositEth function on the L1 to create a deposit. The method is payable, and emits a messageSent event with an empty payload, this makes the sequencer recognize this is a deposit made on the L1 and it mints a new eth coin corresponding to the value of the deposit for the user.
ETH Withdrawals
Withdrawals on the L2 are made by burning the tokens on the L2 via the L2 gateway. Then, the gateway emits a MessageOut receipt, which is part of the block header, allowing the relay of this message to Layer 1.
The Layer 1 Message Portal contract has a relayMessage function (read L2 → L1 messaging for details); which allows for processing L2 messages aimed for L1, in the case of withdrawals, we send a message with the amount corresponding to the value the user has burned on the L2, and hence the Message Portal contract provides the L1 recipient with their funds for withdrawal.
Note: A withdrawal requires the 'Challenge Window' to be cleared before being processed, and hence the user has to wait till the 'Challenge Window' (although there are fast finality gadgets which can bring this down.)
State Updates
Fuel uses Ethereum to submit new state updates. This is done using the State Contract on Layer 1, where the blocks are committed by sending the block hash and block height. The contract also records the timestamp as part of the commitment for a particular block.
These state updates and the data posted as Blobs on Ethereum allow for challenging any state updates sent to the L1.
Challenge Window
The challenge window is the time it takes for a block and related state posted on the L1 to be considered finalized. Finalization means any withdrawal or message part of this block can be processed on the L1. For now, the challenge window for Fuel is seven days.
Techniques like hybrid proving and other fast finality gadgets can reduce the duration of the challenge window; we are actively researching these areas and would encourage you to read Nick Dodson’s post on faster finality gadgets for optimistic rollups.
Hybrid Proving
Fuel believes in a philosophy of zk-pragmatism; rather than playing bisection games on-chain like other rollups (which increase the complexity of the proving system) or sending zk proofs for every bath like zk rollups (which increase the cost per transaction), Fuel makes a hybrid approach for its proving system.
The system runs in an optimistic setting. If someone in the system believes that a fraud state has been sent, they create a zk-proof off-chain of the claim and prove fraud in a single interaction with the L1. This reduces the proving system's complexity and limits the challenge window.
Hybrid proving is being developed, and prototyping is done with RISC-V-based zkVMs like SP-1 and RISC-0. You can read more about the proving system here.
Appendix
Alt-DAs and L1s
We have launched our flagship rollup with Eth as our L1 for settlement and data availability, but Fuel believes in creating a neutral and modular tech stack. The Fuel tech stack can be extended to launch on alt L1s like Bitcoin and Solana and with alt DAs like Celestia and Avail. If someone wants, they can even use the Fuel stack to launch their L1.
We will keep progressing our tech stack to be adaptable in multiple scenarios, resilient, and feasible on consumer-grade hardware.
Blobs
EIP 4844 introduced Blobs as a cheaper way to get Data Availability for Ethereum rollups. Fuel block builders also use blobs, although this is a work in progress.
Fuel blocks are batched together in a bundle, compressed via popular techniques (gzip or zstd), and posted as blobs. Because blobs are fixed in size, uploading has to be done via a series of transactions.
Blobs and their exact implementation are still being finalized and will be live soon, but the above text summarizes the general approach for now.
Security on Fuel
Highlights:
-
Fuel emphasizes security across its rollup ecosystem, particularly through its Security Council, which operates multi-signature wallets to oversee and upgrade various components of the stack. This council plays a critical role in safeguarding the network as Fuel navigates its transition to a fully permissionless system.
-
The project recognizes the current centralization of block building and sequencing as a challenge. To address this, Fuel plans a phased approach to decentralize these processes, beginning with a shared sequencer accessible to all block builders before moving towards fully decentralized block building.
-
Fuel proactively identifies and mitigates potential security attack vectors, including bugs in bridge contracts, Layer 2 client implementations, and the Sway compiler. By collaborating with diverse teams and engaging top security organizations, Fuel works to establish robust security protocols and multiple implementations to reduce the likelihood of vulnerabilities.
-
The platform also tackles application-level bugs by developing secure support libraries in Sway and promoting best practices among developers. This focus on security fosters a more resilient ecosystem for application development.
-
Fuel continuously upgrades its protocol to further enhance security, reducing reliance on the Security Council and minimizing risks associated with multi-signature compromise. The project also prioritizes rigorous auditing and testing of fraud-proving implementations to protect against false claims and ensure that legitimate builders receive fair treatment.
This section discusses the current security of Fuel Ignition and rollups.
Fuel Security Council
Fuel currently has a security council that operates various multi-sigs to upgrade different parts of the stack.
Rollups are in an early stage of development, necessitating a security council to exercise caution before full decentralization. Network issues can be difficult to resolve, making this oversight crucial.
Developing a stack with type-2 security guarantees is a top priority in Fuel's roadmap.
Block Building
Currently, block building and sequencing are centralized. Decentralizing these processes, especially block building, requires further development and careful consideration. Block building rights give the builder access to extract MEV from the system, which can impact user’s transactions and experience on the network.
Fuel is implementing a phased approach to decentralize block builders and sequencers.
Fuel will start with a decentralized sequencer set, i.e., a shared sequencer that block builders can use for all Fuel rollups. Decentralized block building will follow in the next phase.
Security Attack Vectors
In this section, we list various attack vectors for the current system and we explore a path forward to tightening security.
Bridge Contract Bugs
Fuel has a bridge that allows for messaging between L1 and L2; this messaging system creates the base for building a deposit and withdrawal system along with abilities like forced transaction inclusion and calling contracts L2 on L1.
If a bug in the contract implementation on the L1 or L2 compromises the roll-up system, which can include relaying fake messages and transactions from the L1 and L2. A compromise of the mult-sig can also lead to potential malicious upgrades of these contracts.
Fuel undergoes rigorous audits of its smart contracts with the best-in-class security auditors in the space and also participates in bug bounties to keep the possibility of this very low. These issues become more concerning in stage 2 settings, as the stage 1 setting does allow for reverting potential issues regarding bridge contracts.
Layer 2 Client Bugs
Like any software, the Fuel execution client could have bugs that might be exploited to enable unintended behavior. If there is only one implementation of the execution client, a malicious actor might manipulate the system without being caught by a fraud-proof mechanism, as no alternative client would exist to validate or challenge the state. This risk is particularly relevant in L2 solutions that rely on a single execution client for ZK proving games or fraud-proof verification, making it a potential attack vector.
Fuel attempts to strengthen security around such scenarios by inviting various teams to collaborate on the stack and aiming for multiple implementations, followed by rigorous testing and security audits by the best security organizations in the industry.
Sway Compiler Bugs
The Sway Language is a dominant language built on the Fuel VM. A bug in the Sway compiler could allow malicious bytecode to be part of a particular predicate, smart contract, or script, which the implementation didn’t desire. A similar issue was seen in the ETH ecosystem with Vyper, which you can follow here.
Fuel aims to avoid such a scenario by having some of the best talent working on its compiler, followed by rigorous testing and audits by leading security organizations in the industry. In the future, we also aim to have multiple implementations of the compiler, which could help discover a bug in the other implementations.
Application Level Bugs
Application implementations often have bugs because they avoid some required checks or are built on top of libraries with an underlying bug.
Fuel aims to avoid these by creating best-in-class support libraries in Sway, which are well-audited and tested and safe to build on. These Sway libraries also promotes the usage of secure patterns through developer evangelism.
Multisig Compromisation
If compromised, the security council's multi-sig can lead to severe issues, such as malicious upgrades or behavior in various parts of the stack.
Fuel aims to solve this by having a security council with a very high reputation and a lot of social capital attached to it. At the same time, continuous protocol upgrades minimize the need for the security council and always accelerate towards allowing for a stage 2 rollup stack.
Fraud Proving Bugs
A bug in the fraud-proving implementation can cause challenges and slash for block builders who built correct blocks or could allow someone to fail to prove a faulty block. This can result in good builders being slashed or wrong states being finalized.
Fuel aims to solve this by initially correcting any such issues with the help of the security council while aiming for multiple implementations of the fraud-proving client or, if possible, multiple-proving system. The implementation is done with best security practices in mind and with regular system audits.
Fees on Fuel
The Fuel ignition process introduces various fees and costs essential for utilizing the permissionless network. These can be categorized into the following types:
- Transaction Fees: A mandatory fee paid to validators for processing transactions and executing instructions on the network.
- Tip: An optional fee that allows users to boost their transactions in the processing order, ensuring faster execution.
Fuel’s Approach to Transaction Fees
Fee Structure
Fuel operates on a modular execution layer, emphasizing efficiency in gas computation and storage fees. Instead of relying on inflationary rewards, Fuel focuses on sustainable economics driven by transaction fees. Learn more.
Parallel Execution for Low Fees
Fuel’s unique parallel transaction execution significantly reduces congestion. This results in:
- Lower transaction fees.
- Faster settlement times, which encourages a high volume of transactions.
This aligns with the long-term vision of most blockchains to sustain security through transaction fees rather than inflation.
Transaction Fees in the Fuel Network
In the Fuel network, transaction fees are essential for incentivizing block builders to prioritize transactions and for maintaining network security and efficiency. Understanding how transaction fees are calculated and how they are used helps users make informed decisions on how to interact with the network.
What Are Transaction Fees?
Transaction fees in Fuel are the costs that users must pay to process and execute their transactions on the network. These fees are dynamic and depend on several factors, including the gas consumed by the transaction, the gas price, and the gas limit set by the user.
Transaction fees are calculated based on gas consumption during the execution of the transaction. There are two main components that determine the cost:
- Intrinsic Fees: The fundamental costs associated with a transaction, including the byte size, processing of inputs/outputs, predicates and signature verification, and initializing the virtual machine (VM). These fees are incurred regardless of whether the transaction is successfully executed.
- Execution Fees: Costs associated with the computational work performed during the transaction, determined by the complexity of operations such as smart contract execution, data manipulation, and VM computations.
Gas Consumption Breakdown
-
Intrinsic Gas Fees: These cover the basic costs of transaction handling, which include:
- Byte size of the transaction.
- Input/output processing.
- Predicate evaluation and signature verification.
- VM initialization (prior to executing scripts or predicates).
-
Execution Gas Fees: These fees account for the gas consumed during the execution phase of the transaction, such as:
- Smart contract execution.
- Data processing and storage.
- Interactions with decentralized applications (dApps) or other smart contracts.
The total fee for a transaction is calculated using the following formula:
def cost(tx, gasPrice) -> int:
return gas_to_fee(min_gas(tx) + tx.gasLimit - unspentGas, gasPrice)
Where:
min_gas(tx): Minimum gas required for the transaction, covering intrinsic fees.tx.gasLimit: The maximum amount of gas that the user is willing to spend for this transaction.unspentGas: Gas left over after intrinsic costs and execution. This is collected by the block producer as a reward in the Fuel network.gas_to_fee(): Converts the total gas used (after considering min gas, gas limit, and unspent gas) into a fee based on the gasPrice.
The final transaction fee depends on the amount of gas consumed during execution and the gas price specified by the user.
If the transaction uses less gas than the gas limit set by the user, the leftover gas (referred to as unspent gas) is collected by block builder as a reward.
The block gas limit is 30000000
Tips in the Fuel Network
Tips are an additional fee provided by the user to incentivize block builders to prioritize their transaction. In Fuel, the priority of your transaction’s inclusion in the block is determined by both the tip and the max_gas (gas limit) you set for the transaction.
What is a Tip?
A tip is an extra fee that the user adds on top of the minimum transaction fees to increase the likelihood that their transaction will be included in the next block. This tip directly incentivizes the block builder to prioritize the transaction.
- Setting the Tip in a Transaction
To set the tip in a transaction using the Fuel SDK, you can specify it in the transaction parameters. Here’s an example in TypeScript using the fuels library:
import { bn, ScriptTransactionRequest } from 'fuels';
const transactionRequest = new ScriptTransactionRequest({
tip: bn(10), // Sets the tip policy
maxFee: bn(1), // Sets the max fee policy
});
In this example, the tip is set to 10 using the bn function to handle big numbers. The tip is an optional amount added to incentivize the block producer to include the transaction, ensuring faster processing for those willing to pay more.
Fee Structure and Incentives
Fuel uses a dynamic fee model where transaction fees are adjusted based on network congestion, transaction complexity, and the user’s willingness to pay higher fees. Block builders are incentivized based on both the tip and max_gas (gas limit), creating a flexible and efficient system.
Transaction Prioritization
In Fuel, transactions are prioritized by the block builder based on two main factors:
- Tip: The additional gas fee provided by the user to incentivize faster processing of the transaction. A higher tip means higher priority for inclusion in the block.
- max_gas: The maximum gas limit specified by the user for the transaction. A higher max_gas means the transaction may take longer to process, lowering its priority.
The priority of a transaction is:
- Directly proportional to the tip: Higher tips increase the transaction’s priority.
- Inversely proportional to the max_gas: A higher gas limit decreases the transaction’s priority.
This model encourages users to offer a higher tip to ensure quicker inclusion, while also considering the transaction’s gas limit to avoid excessively large transactions.
Unspent Gas and Block Producer Rewards
After a transaction is executed, any leftover gas (unspentGas) is collected by the block producer as a reward.
- UnspentGas: The remaining Gas left over after intrinsic costs and execution. This is collected by the block producer as a reward in the Fuel
- Block Producer Incentives: Block producers are rewarded for processing transactions, both through the minimum fee (guaranteed for each transaction) and the unspent gas collected.
The unspent gas ensures that block producers are incentivized to prioritize transactions with higher tips and to optimize block space for better overall performance.
Example Gas Calculation
Here’s a simplified example of how gas works in Fuel:
- Transaction: A user sends tokens to another account.
- Gas Calculation:
- Compute Gas: CPU work required to validate the transaction.
- Storage Gas: Updating the account balance in the blockchain.
If the gas limit is set to 10,000 and the gas price is 1 gwei, the total fee would be:
- 10,000 x 1 gwei = 10,000 gwei (or 0.00001 ETH ).
When a transaction involves script execution, the system sets up the virtual machine (VM) to run the transaction. It checks the amount of gas available for the transaction and starts running the script step by step.
For each step in the script, the system calculates how much gas it needs. If there isn’t enough gas left to run a step, it stops the process and “reverts” the transaction, meaning nothing changes except for the gas spent. If there’s enough gas, it continues running the script and deducts the gas used. Learn more
At the end of the transaction, the system updates a record (called the receiptsRoot) to show the results of the transaction. This process helps ensure that the transaction is handled efficiently and fairly, with gas being used properly.
Fee Calculation Example
Let’s consider a transaction with the following parameters:
- Intrinsic Gas (
min_gas(tx)): 10,000 gas - Gas Limit (
tx.gasLimit): 50,000 gas - Unspent Gas (
unspentGas): 5,000 gas - Gas Price (
gasPrice): 0.001 Fuel per gas unit
The transaction fee will be calculated as:
min_gas = 10000
gasLimit = 50000
unspentGas = 5000
gasPrice = 0.001
# Total gas used for the transaction
totalGas = min_gas + gasLimit - unspentGas
# Convert gas to fee
fee = gas_to_fee(totalGas, gasPrice)
This fee is the final cost that the user will pay for the transaction, which includes both intrinsic and execution gas fees, adjusted based on the gas price. Note that gasLimit applies to transactions of type Script. gasLimit is not applicable for transactions of type Create and is defined to equal 0 in the above formula.
Transaction Parameters
The following are the available parameters to control transaction behavior:
const txParams: TxParams = {
gasLimit: bn(70935),
maxFee: bn(69242),
tip: bn(100),
};
Explanation of Parameters
- Gas Limit (
gasLimit): The maximum amount of gas you're willing to allow the transaction to consume. If the transaction requires more gas than this limit, it will fail.- Example:
gasLimit: bn(70935)
- Example:
- Max Fee (
maxFee): The maximum amount you're willing to pay for the transaction using the base asset. This allows users to set an upper limit on the transaction fee they are willing to pay, preventing unexpected high costs due to sudden network congestion or fee spikes.- Example:
maxFee: bn(69242)
- Example:
- Tip (
tip): An optional amount of the base asset to incentivize the block producer to include the transaction, ensuring faster processing for those willing to pay more. The value set here will be added to the transaction maxFee.- Example:
tip: bn(100)
- Example:
Fuel’s transaction fee model provides a balanced approach to cost and incentivization:
- Transaction Fees are composed of intrinsic and execution gas fees, with the
gasPricedetermining the final transaction cost. - Tip and
max_gasdetermine transaction priority, allowing users to prioritize their transactions by increasing the tip or adjusting the gas limit.
By setting parameters such as gasLimit, maxFee, tip, and others, users have full control over the cost and priority of their transactions, ensuring a flexible and efficient experience within the Fuel network.
Chapter 3 - Fuel's Future
Fuel Ignition's launch establishes the groundwork for a network of high-performance blockchains and diverse infrastructure, supporting numerous decentralized applications.
Post-mainnet, Fuel will introduce a range of advanced features to elevate blockchain technology.
- 3.1 - A Network of Interconnected L2s & L3s
- 3.2 - Decentralized Block Building
- 3.3 - SNAP Fast Finality Gadget
- 3.4 - State Rehydration
- 3.5 - Data Streaming
A Network of Interconnected L2s & L3s
Fuel Ignition is the beach-head network, introducing the Fuel technology to the world. However, as the technology matures and the ecosystem grows, this technology will be used to power a growing ecosystem of modular rollups. Developers choose to launch independent chains either to customize the stack, to capture economic value, or simply to build an independent ecosystem for community building.
Fuel’s technology already enables the fastest execution and lowest fees, and future development will ensure these chains can easily interoperate with other chains throughout the ecosystem. Fuel’s light-clients, shared sequencer, combined with the SNAP finality gadget (described below) allow these chains to reduce their friction.
Decentralized Block Building
Fully decentralizing block production is the final step towards creating rollups that fully embody the important values of decentralized blockchains.
In addition to simply ensuring that blockchains remain permissionless and censorship-resistant, decentralized block-building will also expand the surface area for how applications can be developed.
SNAP Fast Finality Gadget
One of the most well-known limitations of rollups is the long periods of time needed to send messages from the layer-2 back up to the layer-1 chain. Users of optimistic rollups such as Arbitrum and Optimism face a notable drawback: a week-long wait period for asset withdrawals from the network.While ZK-rollups do offer reduced settlement times, the economics of proving mean that proofs (and therefore withdrawals) can still take up to 24 hours to process.
Fuel has proposed a theoretical new mechanism for substantially reducing the finality time of optimistic rollups. While the full details are outlined in a post on ETHResearch, the mechanism can be understood at a high level as using a bonded committee of operators to attest to both the validity of the chain, as well as the state of Ethereum not being censored.
State Rehydration
Typical blockchain applications treat the chain’s state as a large, public, distributed database. These applications write data to the chain in a similar manner to how a Web2 application would write to a cloud-hosted database. However, this imposes a large burden on the network, which must maintain this data on all nodes. The immutable nature of blockchains means this data is generally stored forever.
Fuel aims to address this issue using a technique known as “state rehydration”. This technique uses the blockchain as a system for maintaining consensus over state commitments, as opposed to a system for syncing a large database.
Specifically, this technique takes advantage of the fact that “calldata” and hashing are relatively inexpensive compared to state storage. State rehydration means that a transaction includes all the state data needed for an action, and this state will be validated against a single on-chain hash as a state commitment. In turn, an application will update the state by hashing the new state values, storing this “dehydrated” commitment in state, and emitting the full state in a log so it can be made available off-chain.
Many current parts of Fuel are considered “state rehydration”, such as predicates which require users to provide the account’s bytecode. Furthermore, some applications have taken state rehydration a step further, using UTXOs and predicates to provide further state reductions.
However, Fuel’s roadmap aims to bring state-rehydration to a level where it can power any arbitrary blockchain application. This requires a full integration between users, block-builders, and off-chain indexers, allowing various parties to pay to reconstruct the state in a completed block. This technique will leverage Fuel’s planned decentralized block-building mechanisms.
Data Streaming
While many projects have focused energy on improving the performance of writing data to blockchains, less attention has been paid to optimizing how to read data from a blockchain. In the future, Fuel will radically restructure how data gets propagated from block-producers out to the various end-users of a network.
Current blockchain applications read data from a network by repeatedly “pinging” an RPC node, asking that node to provide the current state of the network and then checking for updates locally. This method of “polling” for new data is extremely inefficient for all parties, placing computational and network burdens on both the client and server. Furthermore, the “pull” nature of this system means that there will always be some extra latency introduced into the transaction. And in the world of finance, time is money.
Fuel aims to flip the data model on its head, creating a push/subscription model of disseminating data across a network. Fuel will enable block-producers to stream every phase of the transaction supply chain from their own servers, out through a network of lightweight relayers, on to end users. This allows users to have fast access to blockchain data without requiring unnecessary “polling”, and allows financial actors to have the fastest access to the financial information they care about.
Conclusion
Thank you for exploring the Fuel Book. We hope it has provided you with a deeper understanding of the motivations, philosophies, and technical innovations that drive our work. Fuel represents more than just a high-performance blockchain solution—it’s a vision for a decentralized future built on speed, security, and scalability.
As you continue your journey with Fuel, remember that our community thrives on collaboration and shared learning. Whether you're a developer, researcher, or blockchain enthusiast, we invite you to engage with our growing ecosystem, contribute your ideas, and help shape the future of decentralized technology.
Glossary
Contract: Primitives allowing for building stateful applications on Fuel, facilitates complex stateful applications like AMMs, Vaults, etc.
Context: Provides policies that determine what features can some running FuelVM bytecode use, for example the ability to call smart contracts, use persistent storage, etc.
Cryptography: The practice of securing information and communications through mathematical techniques, ensuring data confidentiality, integrity, and authenticity in blockchain systems.
Ephemeral scripting: Scripts or code that are temporary and designed for short-term, single-use purposes. These scripts are typically used for tasks that do not require persistent state or long-term execution and are often discarded or removed after they fulfill their function.
Ethereum Virtual Machine (EVM): A decentralized computing environment that executes smart contracts on the Ethereum blockchain.
Finality: The point at which a transaction is considered permanently recorded on the blockchain and cannot be altered or reversed, providing assurance that it is completed.
Forc: A command-line toolchain that serves as the backbone of Fuel development. It supports everything from compiling Sway smart contracts to managing dependencies and deploying applications.
Fuel Ignition: Will be the first Fuel V2 rollup to go live on Ethereum Mainnet. It aims to surpass traditional EVM rollups by delivering a vastly improved execution design.
Fuel Rust SDK: A developer tool that allows developers to interact with Fuel’s blockchain using the Rust programming language. It offers a seamless experience for creating system-level applications and managing interactions with the Fuel Network.
Fuel Typescript SDK: A developer tool that allows developers to integrate Fuel into web applications. It simplifies interaction with the Fuel blockchain, making it easy for frontend developers to build decentralized applications that interact with Fuel’s infrastructure.
Fuel Virtual Machine (FuelVM): A high-performance execution environment for the Fuel Network that enables parallel transaction processing, achieving up to 21,000 transactions per second per core. It optimizes resource use and minimizes demands on full nodes, enhancing network sustainability and decentralization.
Fuel Wallet SDK: A developer tool that provides developers with the tools to create secure, user-friendly wallets that natively interact with the Fuel ecosystem. It ensures developers can easily build wallets that integrate into decentralized applications.
Interoperability: The ability of different networks to communicate and exchange assets or data seamlessly, enabling cross-chain functionality without intermediaries.
Layer 1: A base blockchain network (e.g., Bitcoin, Ethereum) responsible for processing and finalizing transactions directly on its ledger.
Layer 2: An off-chain scaling solution built on top of a Layer 1 blockchain to improve transaction speed and reduce costs while still relying on Layer 1 for security and finality.
Merkle Tree: A data structure used in blockchains to efficiently and securely verify large sets of transactions, by organizing them into a tree-like structure where each node is a cryptographic hash of its children.
Native Assets: Cryptocurrencies or tokens that are built into and exist directly on a blockchain, serving as the primary currency for that network.
Optimistic Rollup: A Layer 2 scaling solution that processes transactions off-chain while assuming they are valid by default, requiring participants to challenge fraudulent transactions within a specific time frame. If no challenges are made, the transactions are considered valid and finalized on the Layer 1 blockchain.
Parallelism: The ability to process multiple transactions simultaneously.
Predicate: A stateless smart account that allows transactions to execute in parallel without conflict.
Proposer-Builder Separation (PBS): Proposer-builder separation (PBS) is an Ethereum concept designed to enhance network scalability and security by splitting block building responsibilities into two distinct roles: block proposers and block builders.
Rollup: A Layer 2 scaling solution that batches multiple transactions into a single one and processes them off-chain, while still ensuring security and finality on the Layer 1 blockchain.
Scalability: The capability of a blockchain to handle an increasing number of transactions or users without compromising performance, security, or decentralization.
Script: Entrypoint for fuel transactions which dictates what happens as part of a Fuel transaction.
State: All the data a blockchain needs to store and maintain.
State Tree: A data structure used in blockchains to represent the current state of all accounts, smart contracts, and their balances. It allows for efficient storage and retrieval of state information, often enabling quick verification and updates during transaction processing.
Sway: a domain specific language (DSL) for modern blockchain programming which has familiar syntax, grammar and design ideology to Rust while incorporating blockchain specific functionality such as smart contract interface concepts.
Throughput: The number of transactions a blockchain can process within a given time frame, often measured in transactions per second (TPS).
Unspent Transaction Output (UTXO): The model used for tracking asset ownership, contracts, messages and transactions.
Virtual Machine (VM): An environment that executes smart contracts on a blockchain, enabling developers to run code in a decentralized manner without needing to interact with the underlying hardware.
Zero Knowledge: A cryptographic method that allows one party to prove to another that they know a value without revealing the value itself or any other information.
Running a Fuel Ignition Node
Below is a summary of important information to help you get started with running a node for the Layer 2 Fuel Ignition blockchain.
For the latest version of the Fuel client, please visit this link.
Understanding Fuel Ignition's Consensus Mechanism
Fuel Ignition operates on a Proof of Authority (PoA) consensus mechanism. Here’s a brief overview:
Validators: In PoA, there are specific entities, known as validators or "authorities", who are given the responsibility to create new blocks and validate transactions. Unlike other consensus mechanisms like Proof of Work (PoW) or Proof of Stake (PoS), where validators are chosen based on computational power or stake, PoA validators are selected based on their reputation and trustworthiness within the network.
Benefits of PoA: PoA provides faster transaction times and requires less computational power, making it more energy-efficient. The security and integrity of the network are maintained by the trustworthiness of the selected validators.
Hardware Requirements
| Hardware | Minimum | Recommended |
|---|---|---|
| Processor | 2 Cores | 8 Cores |
| Memory | 8 GB | 16 GB |
| Storage | 500 GB | 1 TB |
For low API traffic, an AWS m5.large instance should be sufficient. However, we recommend an AWS m5.4xlarge instance to match the configuration we use for running the network.
For routine tasks such as deploying simple contracts and testing contract interactions locally, you do not need to meet all the hardware requirements listed above.
Getting Started
Depending on your requirements, you can choose one of the following setups:
- Run a Local Fuel Ignition Node: This setup allows you to run a node that operates solely in your local environment.
- Connect to the Fuel Ignition Testnet: With this setup, your local node will connect and sync with Fuel Ignition.
- Connect to the Fuel Ignition Mainnet: With this setup, your local node will connect and sync with the Mainnet version of Fuel Ignition.
Running a local Fuel node
In addition to deploying and testing on the Fuel Testnet, you can also run a local Fuel Node.
There are two types of Fuel networks that can be run:
- In-memory network (without persistence)
- Local network with persistence
Using forc node to run a Local Node
If you wish to still use the
fuel-corebinary directly, you can skip this section and continue with the steps below.
Make sure you have the latest version of fuelup installed or updated. forc node abstracts all the flags and configuration options of the fuel-core binary and is intended for ease of use. To run a local node using forc, you can use the following command:
forc node local
This command will start a local node with the default configuration (with state persistence). The default configuration is highlighted in green at the top of the command output.
If you want to specify a custom configuration, you can use the --help flag to see the available options. For example:
forc node local --help
Dry-run mode
Users of this new plugin may want to review the parameters before running the node. To accommodate this, forc-node includes a dry-run mode, which can be enabled using:
forc-node --dry-run local
Instead of starting the node, this command will print the exact command that would be run, allowing you to verify the parameters beforehand.
Using fuel-core binary to run a local node
If you wish to still use the fuel-core binary directly, you can follow the steps below.
In-memory local node (without state persistence)
An in-memory node does not persist the blockchain state anywhere, it is only stored in memory as long as the node is active and running.
First ensure your environments open files limit ulimit is increased, example:
ulimit -S -n 32768
After ensuring your file limit is increased, to spin-up a local in-memory Fuel node download or copy the local snapshot from here, then run the following command:
fuel-core run --db-type in-memory --debug --snapshot ./your/path/to/chain_config_folder
To deploy a contract to the local node, run the following command:
forc deploy <signing-key> --node-url 127.0.0.1:4000/v1/graphql
Or to deploy with the default signer that is pre-funded by fuel-core:
forc deploy --default-signer --node-url 127.0.0.1:4000/v1/graphql
Chain Configuration
To modify the initial state of the chain, you must configure the state_config.json file in your chain configuration folder.
For simplicity, clone the repository into the directory of your choice.
When using the --snapshot flag later, you can replace ./your/path/to/chain_config_folder with the local folder of the repository you just cloned ./chain-configuration/local/.
To start the node with a custom configuration, you can use the command below:
fuel-core run --snapshot ./your/path/to/chain_config_folder --db-type in-memory --debug
To find an example local chain configuration folder for a specific fuel-core version, refer to the chain-configuration/local repo.
Funding a wallet locally
You can edit the coins array inside state_config.json to modify the initial assets owned by a given address.
The owner address must be a B256 type address (begins with 0x) instead of a Bech32 type (begins with fuel).
The amount is a numerical value. In the example below, the value translates to 1 ETH.
"coins": [
{
"tx_id": "0x0000000000000000000000000000000000000000000000000000000000000001",
"output_index": 0,
"tx_pointer_block_height": 0,
"tx_pointer_tx_idx": 0,
"owner": "0x488284d46414347c78221d3bad71dfebcff61ab2ae26d71129701d50796f714d",
"amount": 1000000000,
"asset_id": "0xf8f8b6283d7fa5b672b530cbb84fcccb4ff8dc40f8176ef4544ddb1f1952ad07"
}
]
Local node (with state persistence)
This node does persist the blockchain state locally. To run a local node with persistence a chain configuration file is required.
To start the node, run the following command:
fuel-core run --ip 127.0.0.1 --port 4000 --snapshot ./your/path/to/chain_config_folder --db-path ./.fueldb --debug
Connecting to the local node from a browser wallet
To connect to the local node using a browser wallet, import the network address as:
http://127.0.0.1:4000/v1/graphql
Running a local Fuel node connected to Testnet using P2P
Fuel is getting ready for our next major client release, which will upgrade the network from version 0.40.x to 0.41.x. This will be a required upgrade for any node operators. We are targeting February 20 for the testnet upgrade. You can upgrade immediately to the 0.41.6 release and sync with the current network using the latest release. This update includes several improvements, such as database optimizations for some API queries. To fully benefit from these changes, you will need to re-sync the chain from the genesis block. While this isn't required for operation, it is recommended for optimal performance.
Installation
To install the Fuel toolchain, you can use the fuelup-init script.
This will install forc, forc-client, forc-fmt, forc-lsp, forc-wallet as well as fuel-core in ~/.fuelup/bin.
curl https://install.fuel.network | sh
Having problems? Visit the installation guide or post your question in our forum.
Getting a Sepolia (Ethereum Testnet) API Key
An API key from any RPC provider that supports the Sepolia network will work. Relayers will help listen to events from the Ethereum network. We recommend either Infura or Alchemy
The endpoints should look like the following:
Infura
https://sepolia.infura.io/v3/{YOUR_API_KEY}
Alchemy
https://eth-sepolia.g.alchemy.com/v2/{YOUR_API_KEY}
Note that using other network endpoints will result in the relayer failing to start.
Using forc node to run a Testnet Node
If you wish to still use the
fuel-corebinary directly, you can skip this section and continue with the steps below.
Make sure you have the latest version of fuelup installed or updated. forc node abstracts all the flags and configuration options of the fuel-core binary and is intended for ease of use. To run a testnet node using forc, you can use the following command:
forc node testnet
This command will prompt for two things:
- You will be asked to create a keypair if you don't already have one.
- You will be asked to provide an Ethereum RPC endpoint that you retrieved from the Getting a Sepolia (Ethereum Testnet) API Key section above.
The default configuration is highlighted in green at the top of the command output.
If you want to specify a custom configuration, you can use the --help flag to see the available options. For example:
forc node testnet --help
Dry-run mode
Users of this new plugin may want to review the parameters before running the node. To accommodate this, forc-node includes a dry-run mode, which can be enabled using:
forc-node --dry-run testnet
Instead of starting the node, this command will print the exact command that would be run, allowing you to verify the parameters beforehand.
Using fuel-core binary to run a local node
If you wish to still use the fuel-core binary directly, you can follow the steps below.
Generating a P2P Key
Generate a new P2P key pairing by running the following command:
fuel-core-keygen new --key-type peering
{
"peer_id":"16Uiu2HAmEtVt2nZjzpXcAH7dkPcFDiL3z7haj6x78Tj659Ri8nrs",
"secret":"b0ab3227974e06d236d265bd1077bb0522d38ead16c4326a5dff2f30edf88496",
"type":"peering"
}
### Do not share or lose this private key! Press any key to complete. ###
Make sure you save this somewhere safe so you don't need to generate a new key pair in the future.
Chain Configuration
To run a local node with persistence, you must have a folder with the following chain configuration files:
For simplicity, clone the repository into the directory of your choice.
When using the --snapshot flag later, you can replace ./your/path/to/chain_config_folder with the ignition-test folder of the repository you just cloned ./chain-configuration/ignition-test/.
Running a Local Node
First ensure your environments open files limit ulimit is increased, example:
ulimit -S -n 32768
Finally to put everything together to start the node, run the following command:
fuel-core run \
--service-name=fuel-sepolia-testnet-node \
--keypair {P2P_PRIVATE_KEY} \
--relayer {ETHEREUM_RPC_ENDPOINT} \
--ip=0.0.0.0 --port=4000 --peering-port=30333 \
--db-path=~/.fuel-sepolia-testnet \
--snapshot ./your/path/to/chain_config_folder \
--utxo-validation --poa-instant false --enable-p2p \
--bootstrap-nodes /dnsaddr/testnet.fuel.network \
--sync-header-batch-size 50 \
--enable-relayer \
--relayer-v2-listening-contracts=0x01855B78C1f8868DE70e84507ec735983bf262dA \
--relayer-da-deploy-height=5827607 \
--relayer-log-page-size=500 \
--sync-block-stream-buffer-size 30
For the full description details of each flag above, run:
fuel-core run --help
Connecting to the local node from a browser wallet
To connect to the local node using a browser wallet, import the network address as:
http://0.0.0.0:4000/v1/graphql
Running a local Fuel node connected to Mainnet using P2P
Fuel is preparing for the next major client release, upgrading the network from version 0.40.x to 0.41.x. This will be a required upgrade for all node operators. The mainnet upgrade is scheduled for March 6. You can upgrade immediately to the 0.41.6 release and sync with the current network using the latest release. This release brings database optimizations for some API queries. To take full advantage of these improvements, we recommend re-syncing the chain from the genesis block. While not mandatory, doing so will ensure the best performance.
Installation
To install the Fuel toolchain, you can use the fuelup-init script.
This will install forc, forc-client, forc-fmt, forc-lsp, forc-wallet as well as fuel-core in ~/.fuelup/bin.
curl https://install.fuel.network | sh
Having problems? Visit the installation guide or post your question in our forum.
Getting a mainnet Ethereum API Key
An API key from any RPC provider that supports the Sepolia network will work. Relayers will help listen to events from the Ethereum network. We recommend either Infura or Alchemy
The endpoints should look like the following:
Infura
https://mainnet.infura.io/v3/{YOUR_API_KEY}
Alchemy
https://eth-mainnet.g.alchemy.com/v2/{YOUR_API_KEY}
Note that using other network endpoints will result in the relayer failing to start.
Using forc node to run a Mainnet Node
If you wish to still use the
fuel-corebinary directly, you can skip this section and continue with the steps below.
Make sure you have the latest version of fuelup installed or updated. forc node abstracts all the flags and configuration options of the fuel-core binary and is intended for ease of use. To run a mainnet node using forc, you can use the following command:
forc node ignition
This command will prompt for two things:
- You will be asked to create a keypair if you don't already have one.
- You will be asked to provide an Ethereum RPC endpoint that you retrieved from the Getting a mainnet Ethereum API Key section above.
The default configuration is highlighted in green at the top of the command output.
If you want to specify a custom configuration, you can use the --help flag to see the available options. For example:
forc node ignition --help
Dry-run mode
Users of this new plugin may want to review the parameters before running the node. To accommodate this, forc-node includes a dry-run mode, which can be enabled using:
forc-node --dry-run ignition
Instead of starting the node, this command will print the exact command that would be run, allowing you to verify the parameters beforehand.
Using fuel-core binary to run a local node
If you wish to still use the fuel-core binary directly, you can follow the steps below.
Generating a P2P Key
Generate a new P2P key pairing by running the following command:
fuel-core-keygen new --key-type peering
{
"peer_id":"16Uiu2HAmEtVt2nZjzpXcAH7dkPcFDiL3z7haj6x78Tj659Ri8nrs",
"secret":"b0ab3227974e06d236d265bd1077bb0522d38ead16c4326a5dff2f30edf88496",
"type":"peering"
}
### Do not share or lose this private key! Press any key to complete. ###
Make sure you save this somewhere safe so you don't need to generate a new key pair in the future.
Chain Configuration
To run a local node with persistence, you must have a folder with the following chain configuration files:
For simplicity, clone the repository into the directory of your choice.
When using the --snapshot flag later, you can replace ./your/path/to/chain_config_folder with the ignition folder of the repository you just cloned ./chain-configuration/ignition/.
Running a Local Node
First ensure your environments open files limit ulimit is increased, example:
ulimit -S -n 32768
Please make sure you have the latest version of the Fuel toolchain installed and properly configured before continuing.
Finally to put everything together to start the node, run the following command:
fuel-core run \
--enable-relayer \
--service-name fuel-mainnet-node \
--keypair {P2P_PRIVATE_KEY} \
--relayer {ETHEREUM_RPC_ENDPOINT} \
--ip=0.0.0.0 --port 4000 --peering-port 30333 \
--db-path ~/.fuel-mainnet \
--snapshot ./your/path/to/chain_config_folder \
--utxo-validation --poa-instant false --enable-p2p \
--bootstrap-nodes /dnsaddr/mainnet.fuel.network \
--sync-header-batch-size 50 \
--relayer-v2-listening-contracts=0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf \
--relayer-da-deploy-height=20620434 \
--relayer-log-page-size=100 \
--sync-block-stream-buffer-size 30
For the full description details of each flag above, run:
fuel-core run --help
Connecting to the local node from a browser wallet
To connect to the local node using a browser wallet, import the network address as:
http://0.0.0.0:4000/v1/graphql
Running a Fuel Sequencer Node or Validator
Below is a summary of key information to help you get started with running a node for the Fuel Sequencer blockchain.
For more details, please refer to the deployment repository.
Hardware Requirements
| Hardware | Minimum | Recommended |
|---|---|---|
| Processor | 4 Cores | 8 Cores |
| Memory | 8 GB | 16 GB |
| Storage | 200 GB | 1 TB |
Port Configuration
Unless otherwise configured, the following ports should be available:
- Sequencer: 26656, 26657, 9090, 1317
- Sidecar: 8080
- Ethereum: 8545, 8546
These components interact with each other, so any changes to the port configuration must be reflected in the corresponding components. Specifically:
- Changes to Sequencer ports must be updated in the Sidecar's runtime flags.
- Changes to the Sidecar port must be updated in the Sequencer's app config.
- Changes to Ethereum ports must be updated in the Sidecar's runtime flags.
Getting Started
Depending on your needs, you can choose one of the following setups:
- Running a Mainnet Fuel Sequencer Node: Your local node will connect to and sync with the mainnet Fuel Sequencer network.
- Running a Mainnet Fuel Sequencer Validator: Your local node will connect to, sync with, and validate transactions on the mainnet Fuel Sequencer network.
- Running a Testnet Fuel Sequencer Node: Your local node will connect to and sync with the testnet Fuel Sequencer network.
- Running a Testnet Fuel Sequencer Validator: Your local node will connect to, sync with, and validate transactions on the testnet Fuel Sequencer network.
Run Sequencer Node
Prerequisites
This guide assumes that Golang is installed to run Cosmovisor. We recommend using version 1.21 or later. You can download it here.
Run the Node
Obtain binary and genesis from this repository:
- Binary from: https://github.com/FuelLabs/fuel-sequencer-deployments/releases/tag/seq-testnet-2-improved-sidecar
- For example:
fuelsequencerd-seq-testnet-2-improved-sidecar-darwin-arm64for Apple Siliconfuelsequencerd-seq-testnet-2-improved-sidecar-darwin-amd64for Linux x64
- For example:
- Genesis from: https://github.com/FuelLabs/fuel-sequencer-deployments/blob/main/seq-testnet-2/genesis.json
Download the right binary based on your architecture to $GOPATH/bin/ with the name fuelsequencerd:
echo $GOPATHto ensure it exists. If not,gomight not be installed.- Make sure that your
GOPATHis set properly in your.bashrcor.zshrcfile. Runsource ~/.bashrcorsource ~/.zshrcto apply the changes.
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
mkdir $GOPATH/bin/if the directory does not exist.wget <url/to/binary>to download the binary, or any equivalent approach. For example:
wget https://github.com/FuelLabs/fuel-sequencer-deployments/releases/download/seq-testnet-2-improved-sidecar/fuelsequencerd-seq-testnet-2-improved-sidecar-darwin-arm64
cp <binary> $GOPATH/bin/fuelsequencerdto copy the binary to theGOPATH/bin/directory.chmod +x $GOPATH/bin/fuelsequencerdto make the binary executable.fuelsequencerd versionto verify that the binary is working.
Try the binary:
fuelsequencerd version # expect seq-testnet-2-improved-sidecar
Initialise node directory:
fuelsequencerd init <node-name> --chain-id seq-testnet-2
Copy the downloaded genesis file to ~/.fuelsequencer/config/genesis.json:
cp <path/to/genesis.json> ~/.fuelsequencer/config/genesis.json
Configure the node (part 1: ~/.fuelsequencer/config/app.toml):
- Set
minimum-gas-prices = "10test". - Configure
[sidecar]:- Ensure that
enabled = false.
- Ensure that
Configure the node (part 2: ~/.fuelsequencer/config/config.toml):
- Configure
[p2p]:- Set
persistent_peers = "3a0b4118c01addd33d5add81783805d5add2fb17@80.64.208.17:26656".
- Set
- Configure
[mempool]:- Set
max_tx_bytes = 1153434(1.1MiB) - Set
max_txs_bytes = 23068670(~22MiB)
- Set
- Configure
[rpc]:- Set
max_body_bytes = 1153434(optional - relevant for public RPC).
- Set
Note: Ensuring consistent CometBFT mempool parameters across all network nodes is important to reduce transaction delays. This includes
mempool.size,mempool.max_txs_bytes, andmempool.max_tx_bytesin config.toml andminimum-gas-pricesin app.toml, as pointed out above.
Install Cosmovisor
To install Cosmovisor, run go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
Set the environment variables:
If you're running on a zsh terminal...
echo "# Setup Cosmovisor" >> ~/.zshrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.zshrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.zshrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.zshrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.zshrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.zshrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.zshrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.zshrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.zshrc
If you're running on a bash terminal...
echo "# Setup Cosmovisor" >> ~/.bashrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.bashrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.bashrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.bashrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.bashrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.bashrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.bashrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.bashrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.bashrc
You can now test that cosmovisor was installed properly:
cosmovisor version
Initialise Cosmovisor directories (hint: whereis fuelsequencerd for the path):
cosmovisor init <path/to/fuelsequencerd>
At this point cosmovisor run will be the equivalent of running fuelsequencerd, however you should not run the node for now.
Configure State Sync
State Sync allows a node to get synced up quickly.
To configure State Sync, you will need to set these values in ~/.fuelsequencer/config/config.toml under [statesync]:
enable = trueto enable State Syncrpc_servers = ...trust_height = ...trust_hash = ...
The last three values can be obtained from the explorer.
You will need to specify at least two comma-separated RPC servers in rpc_servers. You can either refer to the list of alternate RPC servers above or use the same one twice.
Running the Sequencer
At this point you should already be able to run cosmovisor run start to run the Sequencer. However, it is highly recommended to run the Sequencer as a background service.
Some examples are provided below for Linux and Mac. You will need to replicate the environment variables defined when setting up Cosmovisor.
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sequencer Node
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=/home/<USER>/go/bin/cosmovisor run start
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
Environment="DAEMON_NAME=fuelsequencerd"
Environment="DAEMON_HOME=/home/<USER>/.fuelsequencer"
Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=true"
Environment="DAEMON_LOG_BUFFER_SIZE=512"
Environment="DAEMON_RESTART_AFTER_UPGRADE=true"
Environment="UNSAFE_SKIP_BACKUP=true"
Environment="DAEMON_SHUTDOWN_GRACE=15s"
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sequencer</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/cosmovisor</string>
<string>run</string>
<string>start</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>EnvironmentVariables</key>
<dict>
<key>DAEMON_NAME</key>
<string>fuelsequencerd</string>
<key>DAEMON_HOME</key>
<string>/Users/[User]/.fuelsequencer</string>
<key>DAEMON_ALLOW_DOWNLOAD_BINARIES</key>
<string>true</string>
<key>DAEMON_LOG_BUFFER_SIZE</key>
<string>512</string>
<key>DAEMON_RESTART_AFTER_UPGRADE</key>
<string>true</string>
<key>UNSAFE_SKIP_BACKUP</key>
<string>true</string>
<key>DAEMON_SHUTDOWN_GRACE</key>
<string>15s</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.err</string>
</dict>
</plist>
References
Based on material from:
- https://docs.cosmos.network/main/tooling/cosmovisor
- https://docs.osmosis.zone/overview/validate/joining-mainnet#set-up-cosmovisor
Run Sequencer Validator
Typical Setup
The validator setup will consist of a Fuel Sequencer, Sidecar, and a connection to an Ethereum Sepolia Node.

Prerequisites
This guide assumes that Golang is installed to run Cosmovisor. We recommend using version 1.21 or later. You can download it here.
Run an Ethereum Sepolia Full Node
To ensure the highest performance and reliability of the Sequencer infrastructure, running your own Ethereum Sepolia full node is a requirement. Avoiding the use of third-party services for Ethereum node operations significantly helps the Sequencer network's liveness. Please note these recommended node configurations:
--syncmode=snap
--gcmode=full
Configure the Sequencer
Obtain binary and genesis from this repository:
- Binary from: https://github.com/FuelLabs/fuel-sequencer-deployments/releases/tag/seq-testnet-2-improved-sidecar
- For example:
fuelsequencerd-seq-testnet-2-improved-sidecar-darwin-arm64for Apple Siliconfuelsequencerd-seq-testnet-2-improved-sidecar-darwin-amd64for Linux x64
- For example:
- Genesis from: https://github.com/FuelLabs/fuel-sequencer-deployments/blob/main/seq-testnet-2/genesis.json
Download the right binary based on your architecture to $GOPATH/bin/ with the name fuelsequencerd:
echo $GOPATHto ensure it exists. If not,gomight not be installed.- Make sure that your
GOPATHis set properly in your.bashrcor.zshrcfile. Runsource ~/.bashrcorsource ~/.zshrcto apply the changes.
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
mkdir $GOPATH/bin/if the directory does not exist.wget <url/to/binary>to download the binary, or any equivalent approach. For example:
wget https://github.com/FuelLabs/fuel-sequencer-deployments/releases/download/seq-testnet-2-improved-sidecar/fuelsequencerd-seq-testnet-2-improved-sidecar-darwin-arm64
cp <binary> $GOPATH/bin/fuelsequencerdto copy the binary to theGOPATH/bin/directory.chmod +x $GOPATH/bin/fuelsequencerdto make the binary executable.fuelsequencerd versionto verify that the binary is working.
Try the binary:
fuelsequencerd version # expect seq-testnet-2-improved-sidecar
Initialise the node directory, giving your node a meaningful name:
fuelsequencerd init <node-name> --chain-id seq-testnet-2
Copy the downloaded genesis file to ~/.fuelsequencer/config/genesis.json:
cp <path/to/genesis.json> ~/.fuelsequencer/config/genesis.json
Configure the node (part 1: ~/.fuelsequencer/config/app.toml):
- Set
minimum-gas-prices = "10test". - Configure
[sidecar]:- Ensure that
enabled = true. - Ensure that
addressis where the Sidecar will run.
- Ensure that
- Configure
[api]:- Set
swagger=true(optional). - Set
rpc-max-body-bytes = 1153434(optional - relevant for public REST).
- Set
- Configure
[commitments]:- Set
api-enabled = true(optional - relevant for public REST).
- Set
- Configure
[state-sync]:- Set
snapshot-interval = 1000(optional - to provide state-sync service).
- Set
- Configure:
- Set
rpc-read-timeout = 10(optional - relevant for public REST). - Set
rpc-write-timeout = 0(optional - relevant for public REST).
- Set
WARNING: leaving the
[commitments]API accessible to anyone can lead to DoS! It is highly recommended to handle whitelisting or authentication by a reverse proxy like Traefik for gRPC if the commitments API is enabled.
Configure the node (part 2: ~/.fuelsequencer/config/config.toml):
- Configure
[p2p]:- Set
persistent_peers = "fc5fd264190e4a78612ec589994646268b81f14e@80.64.208.207:26656".
- Set
- Configure
[mempool]:- Set
max_tx_bytes = 1258291(1.2MiB) - Set
max_txs_bytes = 23068672(22MiB)
- Set
- Configure
[rpc]:- Set
max_body_bytes = 1153434(optional - relevant for public RPC).
- Set
Note: Ensuring consistent CometBFT mempool parameters across all network nodes is important to reduce transaction delays. This includes
mempool.size,mempool.max_txs_bytes, andmempool.max_tx_bytesin config.toml andminimum-gas-pricesin app.toml, as pointed out above.
Install Cosmovisor
To install Cosmovisor, run go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
Set the environment variables:
If you're running on a zsh terminal...
echo "# Setup Cosmovisor" >> ~/.zshrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.zshrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.zshrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.zshrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.zshrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.zshrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.zshrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.zshrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.zshrc
If you're running on a bash terminal...
echo "# Setup Cosmovisor" >> ~/.bashrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.bashrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.bashrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.bashrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.bashrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.bashrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.bashrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.bashrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.bashrc
You can now test that cosmovisor was installed properly:
cosmovisor version
Initialise Cosmovisor directories (hint: whereis fuelsequencerd for the path):
cosmovisor init <path/to/fuelsequencerd>
At this point cosmovisor run will be the equivalent of running fuelsequencerd, however you should not run the node for now.
Configure State Sync
State Sync allows a node to get synced up quickly.
To configure State Sync, you will need to set these values in ~/.fuelsequencer/config/config.toml under [statesync]:
enable = trueto enable State Syncrpc_servers = ...trust_height = ...trust_hash = ...
The last three values can be obtained from the explorer.
You will need to specify at least two comma-separated RPC servers in rpc_servers. You can either refer to the list of alternate RPC servers above or use the same one twice.
Run the Sidecar
At this point you should already be able to run fuelsequencerd start-sidecar with the right flags, to run the Sidecar. However, it is highly recommended to run the Sidecar as a background service.
It is also very important to ensure that you provide all the necessary flags when running the Sidecar to ensure that it can connect to an Ethereum node and to the Sequencer node, and is also accessible by the Sequencer node. The most important flags are:
host: host for the gRPC server to listen onport: port for the gRPC server to listen oneth_ws_url: Ethereum node WebSocket endpointeth_rpc_url: Ethereum node RPC endpointeth_contract_address: address in hex format of the contract to monitor for logs. This MUST be set to0x0E5CAcD6899a1E2a4B4E6e0c8a1eA7feAD3E25eD.sequencer_grpc_url: Sequencer node gRPC endpoint
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sidecar
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=<HOME>/go/bin/fuelsequencerd start-sidecar \
--host "0.0.0.0" \
--sequencer_grpc_url "127.0.0.1:9090" \
--eth_ws_url "<ETHEREUM_NODE_WS>" \
--eth_rpc_url "<ETHEREUM_NODE_RPC>" \
--eth_contract_address "0x0E5CAcD6899a1E2a4B4E6e0c8a1eA7feAD3E25eD"
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sidecar</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/fuelsequencerd</string>
<string>start-sidecar</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--sequencer_grpc_url</string>
<string>127.0.0.1:9090</string>
<string>--eth_ws_url</string>
<string>[ETHEREUM_NODE_WS]</string>
<string>--eth_rpc_url</string>
<string>[ETHEREUM_NODE_RPC]</string>
<string>--eth_contract_address</string>
<string>0x0E5CAcD6899a1E2a4B4E6e0c8a1eA7feAD3E25eD</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sidecar.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sidecar.err</string>
</dict>
</plist>
Run the Sequencer
At this point you should already be able to run cosmovisor run start to run the Sequencer. However, it is highly recommended to run the Sequencer as a background service.
Some examples are provided below for Linux and Mac. You will need to replicate the environment variables defined when setting up Cosmovisor.
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sequencer Node
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=/home/<USER>/go/bin/cosmovisor run start
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
Environment="DAEMON_NAME=fuelsequencerd"
Environment="DAEMON_HOME=/home/<USER>/.fuelsequencer"
Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=true"
Environment="DAEMON_LOG_BUFFER_SIZE=512"
Environment="DAEMON_RESTART_AFTER_UPGRADE=true"
Environment="UNSAFE_SKIP_BACKUP=true"
Environment="DAEMON_SHUTDOWN_GRACE=15s"
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sequencer</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/cosmovisor</string>
<string>run</string>
<string>start</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>EnvironmentVariables</key>
<dict>
<key>DAEMON_NAME</key>
<string>fuelsequencerd</string>
<key>DAEMON_HOME</key>
<string>/Users/[User]/.fuelsequencer</string>
<key>DAEMON_ALLOW_DOWNLOAD_BINARIES</key>
<string>true</string>
<key>DAEMON_LOG_BUFFER_SIZE</key>
<string>512</string>
<key>DAEMON_RESTART_AFTER_UPGRADE</key>
<string>true</string>
<key>UNSAFE_SKIP_BACKUP</key>
<string>true</string>
<key>DAEMON_SHUTDOWN_GRACE</key>
<string>15s</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.err</string>
</dict>
</plist>
Creating an Account
To run a validator, you will need to have a Sequencer account address. Generate an address with a key name:
fuelsequencerd keys add <NAME> # for a brand new key
# or
fuelsequencerd keys add <NAME> --recover # to create from a mnemonic
This will give you an output with an address (e.g. fuelsequencer1l7qk9umswg65av0zygyymgx5yg0fx4g0dpp2tl) and a private mnemonic, if you generated a brand new key. Store the mnemonic safely.
Fuel Sequencer addresses also have an Ethereum-compatible (i.e. hex) format. To generate the hex address corresponding to your Sequencer address, run the following:
fuelsequencerd keys parse <ADDRESS>
This will give an output in this form:
bytes: FF8162F37072354EB1E222084DA0D4221E93550F
human: fuelsequencer
Adding the 0x prefix to the address in the first line gives you your Ethereum-compatible address, used to deposit into and interact with your Sequencer address from Ethereum. In this case, it's 0xFF8162F37072354EB1E222084DA0D4221E93550F.
Funding the Account
Ensure your Ethereum account (EOA) has sufficient ETH to cover gas fees.
Important Addresses
- FUEL Token:
0xd7Fc4e8FB2c05567C313f4C9b9e07641a361a550 - Sequencer Interface (Bridge):
0x742C478a1951257E83d3aC8f3DFB3A8e6AB9a2E4
Token Faucet
To obtain testnet tokens, visit Fuel's official Ethereum testnet staking UI with any Ethereum EOA that has not previously received FUEL tokens from the faucet.
Click "Faucet Fuel Token" to receive 100 FUEL tokens for testing.

Token Approval
Before proceeding, you must approve the Fuel token contract to allow the transfer of tokens.
In the Fuel Token Etherscan contract UI, use the approve (0x095ea7b3) function:
-
Spender (
address): Set this to the Sequencer Interface (Bridge) address:0x742C478a1951257E83d3aC8f3DFB3A8e6AB9a2E4. -
Value (
uint256): Enter the number of tokens to approve, including 9 additional decimal places. For unlimited approval, use:0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Bridging Tokens
To bridge tokens, connect your Ethereum wallet by clicking "Connect to Web3" in the top left. Then, use the depositFor (0x36efd6f) function to fund your sequencer account.
Transfer your FUEL tokens using the Sequencer Interface (Bridge) Etherscan UI.

- Amount (
uint256): Enter the number of tokens to send, including 9 additional decimal places. - Recipient address: Enter the Ethereum-compatible address you generated earlier (e.g.,
0xFF8162F37072354EB1E222084DA0D4221E93550F).
Click "Write" to confirm the transaction. The transfer may take ~20 minutes to process.
Verifying Funds
To verify your funds, enter your sequencer account address (i.e. fuelsequencer1l7qk9umswg65av0zygyymgx5yg0fx4g0dpp2tl) in the testnet block explorer.

⚠ WARNING: Always test with a small transfer before bridging FUEL tokens.
Withdrawals
Withdrawals can be easily initiated through the CLI and will be settled on Sepolia approximately 3 days later, as the commitment and bridge finalizations must be completed first.
Identify the account from which you wish to withdraw. Use the following command to list all previously created account names matching your account address above:
fuelsequencerd keys list
Example output:
address: fuelsequencer1zzu4804kp6m6whzza6r75g7mnme2ahqkjuw4kf
name: my-testnet-validator
pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Al6W+Ttrscm/8njeMOt79T0BOdphfWGXrDLij+O3g19N"}'
type: local
Verify that this is the correct address and account name from which you wish to withdraw.
To initiate the withdrawal, use the following command where <eth-destination-address> is any Sepolia address you wish to withdraw to and <amount-in-fuel> is the amount of TEST (FUEL) you wish to withdraw:
Note: The amount in TEST (FUEL) must include 9 decimal places.
fuelsequencerd tx bridge withdraw-to-ethereum <eth-destination-address> <amount-in-fuel> \
--from=<key> \
--gas-prices=10test \
--gas=auto \
--gas-adjustment 1.5 \
--node="https://testnet-rpc-fuel-seq.simplystaking.xyz/" \
--chain-id="seq-testnet-2"
For example:
fuelsequencerd tx bridge withdraw-to-ethereum 0xd70080dE4535db4A64798a23619Db64fB28fD079 1test \
--from=my-testnet-validator \
--gas-prices=10test \
--gas=auto \
--gas-adjustment 1.5 \
--node="https://testnet-rpc-fuel-seq.simplystaking.xyz/" \
--chain-id="seq-testnet-2"
Review the transaction details and confirm the transaction by typing yes when prompted:
gas estimate: 106713
auth_info:
fee:
amount:
- amount: "1067130"
denom: test
gas_limit: "106713"
granter: ""
payer: ""
signer_infos: []
tip: null
body:
extension_options: []
memo: ""
messages:
- '@type': /fuelsequencer.bridge.v1.MsgWithdrawToEthereum
amount:
amount: "1"
denom: test
from: fuelsequencer1zzu4804kp6m6whzza6r75g7mnme2ahqkjuw4kf
to: 0xd70080dE4535db4A64798a23619Db64fB28fD079
non_critical_extension_options: []
timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]:
If the transaction is successful, you will receive a transaction hash, which you can paste and monitor the status of your withdrawal here:
code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: ""
timestamp: ""
tx: null
txhash: FF51288FB916CEE4538E17FB70E438278143FAD2B613D98362A562B02C07253F

After verifying your withdrawal on the shared sequencer explorer, visit Simply Staking and connect your wallet. Navigate to the Withdrawal tab on the right to monitor the progress of your withdrawal.

Once the 3-day waiting period has passed, the withdrawal will require manual action to pull the funds out.
Create the Validator
To create the validator, a prerequisite is to have at least 1TEST, with enough extra to pay for gas fees. You can check your balance from the explorer.
Once you have TEST tokens, run the following to create a validator, using the name of the account that you created in the previous steps:
fuelsequencerd tx staking create-validator path/to/validator.json \
--from <NAME> \
--gas auto \
--gas-prices 10test \
--gas-adjustment 1.5 \
--chain-id seq-testnet-2
...where validator.json contains:
{
"pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"<PUBKEY>"},
"amount": "1000000000test",
"moniker": "<MONIKER>",
"identity": "<OPTIONAL-IDENTITY>",
"website": "<OPTIONAL-WEBSITE>",
"security": "<OPTIONAL-EMAIL>",
"details": "<OPTIONAL-DETAILS>",
"commission-rate": "0.05",
"commission-max-rate": "<MAX-RATE>",
"commission-max-change-rate": "<MAX-CHANGE-RATE>",
"min-self-delegation": "1"
}
...where the pubkey can be obtained using fuelsequencerd tendermint show-validator.
What to Expect
- The Sequencer should show block syncing.
- The Sidecar should show block extraction. Occasionally it also receives requests for events.
Tendermint KMS
If you will be using tmkms, make sure that in the config:
- Chain ID is set to
seq-testnet-2wherever applicable account_key_prefix = "fuelsequencerpub"consensus_key_prefix = "fuelsequencervalconspub"sign_extensions = trueprotocol_version = "v0.34"
Additional Advanced Configuration
Sidecar flags:
development: starts the sidecar in development mode.eth_max_block_range: max number of Ethereum blocks queried at one go.eth_min_logs_query_interval: minimum wait between successive queries for logs.unsafe_eth_start_block: the Ethereum block to start querying from.unsafe_eth_end_block: the last Ethereum block to query. Incorrect use can cause the validator to propose empty blocks, leading to slashing!sequencer_path_to_cert_file: path to the certificate file of the Sequencer infrastructure for secure communication. Specify this value if the Sequencer infrastructure was set up using TLS.sidecar_path_to_cert_file: path to the certificate file of the sidecar server for secure communication. Specify this value if you want to set up a sidecar server with TLS.sidecar_path_to_key_file: path to the private key file of the sidecar server for secure communication. Specify this value if you want to set up a sidecar server with TLS.prometheus_enabled: enables serving of prometheus metrics.prometheus_listen_address: address to listen for prometheus collectors (default ":8081").prometheus_max_open_connections: max number of simultaneous connections (default 3).prometheus_namespace: instrumentation namespace (default "sidecar").prometheus_read_header_timeout: amount of time allowed to read request headers (default 10s).prometheus_write_timeout: maximum duration before timing out writes of the response (default 10s).
Sidecar client flags:
sidecar_grpc_url: the sidecar's gRPC endpoint.query_timeout: how long to wait before the request times out.
References
Based on material from:
- https://docs.cosmos.network/main/tooling/cosmovisor
- https://docs.osmosis.zone/overview/validate/joining-testnet#set-up-cosmovisor
Run Sequencer Node
Prerequisites
This guide assumes that Golang is installed to run Cosmovisor. We recommend using version 1.21 or later. You can download it here.
Run the Node
Obtain binary and genesis from this repository:
- Binary from: https://github.com/FuelLabs/fuel-sequencer-deployments/releases/tag/seq-mainnet-1.2-improved-sidecar
- For example:
fuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-arm64for Apple Siliconfuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-amd64for Linux x64
- For example:
- Genesis from: https://github.com/FuelLabs/fuel-sequencer-deployments/blob/main/seq-mainnet-1/genesis.json
Download the right binary based on your architecture to $GOPATH/bin/ with the name fuelsequencerd:
echo $GOPATHto ensure it exists. If not,gomight not be installed.- Make sure that your
GOPATHis set properly in your.bashrcor.zshrcfile. Runsource ~/.bashrcorsource ~/.zshrcto apply the changes.
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
mkdir $GOPATH/bin/if the directory does not exist.wget <url/to/binary>to download the binary, or any equivalent approach. For example:
wget https://github.com/FuelLabs/fuel-sequencer-deployments/releases/download/seq-mainnet-1.2-improved-sidecar/fuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-arm64
cp <binary> $GOPATH/bin/fuelsequencerdto copy the binary to theGOPATH/bin/directory.chmod +x $GOPATH/bin/fuelsequencerdto make the binary executable.fuelsequencerd versionto verify that the binary is working.
Try the binary:
fuelsequencerd version # expect seq-mainnet-1.2-improved-sidecar
Initialise the node directory, giving your node a meaningful name:
fuelsequencerd init <node-name> --chain-id seq-mainnet-1
Copy the downloaded genesis file to ~/.fuelsequencer/config/genesis.json:
cp <path/to/genesis.json> ~/.fuelsequencer/config/genesis.json
Configure the node (part 1: ~/.fuelsequencer/config/app.toml):
- Set
minimum-gas-prices = "10fuel". - Configure
[sidecar]:- Ensure that
enabled = false.
- Ensure that
Configure the node (part 2: ~/.fuelsequencer/config/config.toml):
- Configure
[p2p]:- Set
persistent_peers = "fc5fd264190e4a78612ec589994646268b81f14e@80.64.208.207:26656".
- Set
- Configure
[mempool]:- Set
max_tx_bytes = 1258291(1.2MiB) - Set
max_txs_bytes = 23068672(22MiB)
- Set
- Configure
[rpc]:- Set
max_body_bytes = 1153434(optional - relevant for public RPC).
- Set
Note: Ensuring consistent CometBFT mempool parameters across all network nodes is important to reduce transaction delays. This includes
mempool.size,mempool.max_txs_bytes, andmempool.max_tx_bytesin config.toml andminimum-gas-pricesin app.toml, as pointed out above.
Install Cosmovisor
To install Cosmovisor, run go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
Set the environment variables:
If you're running on a zsh terminal...
echo "# Setup Cosmovisor" >> ~/.zshrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.zshrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.zshrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.zshrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.zshrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.zshrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.zshrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.zshrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.zshrc
If you're running on a bash terminal...
echo "# Setup Cosmovisor" >> ~/.bashrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.bashrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.bashrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.bashrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.bashrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.bashrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.bashrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.bashrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.bashrc
You can now test that cosmovisor was installed properly:
cosmovisor version
Initialise Cosmovisor directories (hint: whereis fuelsequencerd for the path):
cosmovisor init <path/to/fuelsequencerd>
At this point cosmovisor run will be the equivalent of running fuelsequencerd, however you should not run the node for now.
Configure State Sync
State Sync allows a node to get synced up quickly.
To configure State Sync, you will need to set these values in ~/.fuelsequencer/config/config.toml under [statesync]:
enable = trueto enable State Syncrpc_servers = ...trust_height = ...trust_hash = ...
The last three values can be obtained from the explorer.
You will need to specify at least two comma-separated RPC servers in rpc_servers. You can either refer to the list of alternate RPC servers above or use the same one twice.
Running the Sequencer
At this point you should already be able to run cosmovisor run start to run the Sequencer. However, it is highly recommended to run the Sequencer as a background service.
Some examples are provided below for Linux and Mac. You will need to replicate the environment variables defined when setting up Cosmovisor.
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sequencer Node
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=/home/<USER>/go/bin/cosmovisor run start
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
Environment="DAEMON_NAME=fuelsequencerd"
Environment="DAEMON_HOME=/home/<USER>/.fuelsequencer"
Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=true"
Environment="DAEMON_LOG_BUFFER_SIZE=512"
Environment="DAEMON_RESTART_AFTER_UPGRADE=true"
Environment="UNSAFE_SKIP_BACKUP=true"
Environment="DAEMON_SHUTDOWN_GRACE=15s"
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sequencer</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/cosmovisor</string>
<string>run</string>
<string>start</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>EnvironmentVariables</key>
<dict>
<key>DAEMON_NAME</key>
<string>fuelsequencerd</string>
<key>DAEMON_HOME</key>
<string>/Users/[User]/.fuelsequencer</string>
<key>DAEMON_ALLOW_DOWNLOAD_BINARIES</key>
<string>true</string>
<key>DAEMON_LOG_BUFFER_SIZE</key>
<string>512</string>
<key>DAEMON_RESTART_AFTER_UPGRADE</key>
<string>true</string>
<key>UNSAFE_SKIP_BACKUP</key>
<string>true</string>
<key>DAEMON_SHUTDOWN_GRACE</key>
<string>15s</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.err</string>
</dict>
</plist>
References
Based on material from:
- https://docs.cosmos.network/main/tooling/cosmovisor
- https://docs.osmosis.zone/overview/validate/joining-mainnet#set-up-cosmovisor
Run Sequencer Validator
Typical Setup
The validator setup will consist of a Fuel Sequencer, Sidecar, and a connection to an Ethereum Mainnet Node.

Prerequisites
The guide assumes that Golang is installed in order to run Cosmovisor. We recommend installing version 1.21+.
Run an Ethereum Mainnet Full Node
To ensure the highest performance and reliability of the Sequencer infrastructure, running your own Ethereum Mainnet full node is a requirement. Avoiding the use of third-party services for Ethereum node operations significantly helps the Sequencer network's liveness. Please note these recommended node configurations:
--syncmode=snap
--gcmode=full
Configure the Sequencer
Obtain binary and genesis from this repository:
- Binary from: https://github.com/FuelLabs/fuel-sequencer-deployments/releases/tag/seq-mainnet-1.2-improved-sidecar
- For example:
fuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-arm64for Apple Siliconfuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-amd64for Linux x64
- For example:
- Genesis from: https://github.com/FuelLabs/fuel-sequencer-deployments/blob/main/seq-mainnet-1/genesis.json
Download the right binary based on your architecture to $GOPATH/bin/ with the name fuelsequencerd:
echo $GOPATHto ensure it exists. If not,gomight not be installed.- Make sure that your
GOPATHis set properly in your.bashrcor.zshrcfile. Runsource ~/.bashrcorsource ~/.zshrcto apply the changes.
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin
mkdir $GOPATH/bin/if the directory does not exist.wget <url/to/binary>to download the binary, or any equivalent approach. For example:
wget https://github.com/FuelLabs/fuel-sequencer-deployments/releases/download/seq-mainnet-1.2-improved-sidecar/fuelsequencerd-seq-mainnet-1.2-improved-sidecar-darwin-arm64
cp <binary> $GOPATH/bin/fuelsequencerdto copy the binary to theGOPATH/bin/directory.chmod +x $GOPATH/bin/fuelsequencerdto make the binary executable.fuelsequencerd versionto verify that the binary is working.
Try the binary:
fuelsequencerd version # expect seq-mainnet-1.2-improved-sidecar
Initialise the node directory, giving your node a meaningful name:
fuelsequencerd init <node-name> --chain-id seq-mainnet-1
Copy the downloaded genesis file to ~/.fuelsequencer/config/genesis.json:
cp <path/to/genesis.json> ~/.fuelsequencer/config/genesis.json
Configure the node (part 1: ~/.fuelsequencer/config/app.toml):
- Set
minimum-gas-prices = "10fuel". - Configure
[sidecar]:- Ensure that
enabled = true. - Ensure that
addressis where the Sidecar will run.
- Ensure that
- Configure
[api]:- Set
swagger=true(optional). - Set
rpc-max-body-bytes = 1153434(optional - relevant for public REST).
- Set
- Configure
[commitments]:- Set
api-enabled = true(optional - relevant for public REST).
- Set
- Configure
[state-sync]:- Set
snapshot-interval = 1000(optional - to provide state-sync service).
- Set
- Configure:
- Set
rpc-read-timeout = 10(optional - relevant for public REST). - Set
rpc-write-timeout = 0(optional - relevant for public REST).
- Set
WARNING: leaving the
[commitments]API accessible to anyone can lead to DoS! It is highly recommended to handle whitelisting or authentication by a reverse proxy like Traefik for gRPC if the commitments API is enabled.
Configure the node (part 2: ~/.fuelsequencer/config/config.toml):
- Configure
[p2p]:- Set
persistent_peers = "fc5fd264190e4a78612ec589994646268b81f14e@80.64.208.207:26656".
- Set
- Configure
[mempool]:- Set
max_tx_bytes = 1258291(1.2MiB) - Set
max_txs_bytes = 23068672(22MiB)
- Set
- Configure
[rpc]:- Set
max_body_bytes = 1153434(optional - relevant for public RPC).
- Set
Note: Ensuring consistent CometBFT mempool parameters across all network nodes is important to reduce transaction delays. This includes
mempool.size,mempool.max_txs_bytes, andmempool.max_tx_bytesin config.toml andminimum-gas-pricesin app.toml, as pointed out above.
Install Cosmovisor
To install Cosmovisor, run go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
Set the environment variables:
If you're running on a zsh terminal...
echo "# Setup Cosmovisor" >> ~/.zshrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.zshrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.zshrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.zshrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.zshrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.zshrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.zshrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.zshrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.zshrc
If you're running on a bash terminal...
echo "# Setup Cosmovisor" >> ~/.bashrc
echo "export DAEMON_NAME=fuelsequencerd" >> ~/.bashrc
echo "export DAEMON_HOME=$HOME/.fuelsequencer" >> ~/.bashrc
echo "export DAEMON_ALLOW_DOWNLOAD_BINARIES=true" >> ~/.bashrc
echo "export DAEMON_LOG_BUFFER_SIZE=512" >> ~/.bashrc
echo "export DAEMON_RESTART_AFTER_UPGRADE=true" >> ~/.bashrc
echo "export UNSAFE_SKIP_BACKUP=true" >> ~/.bashrc
echo "export DAEMON_SHUTDOWN_GRACE=15s" >> ~/.bashrc
# You can check https://docs.cosmos.network/main/tooling/cosmovisor for more configuration options.
Apply to your current session: source ~/.bashrc
You can now test that cosmovisor was installed properly:
cosmovisor version
Initialise Cosmovisor directories (hint: whereis fuelsequencerd for the path):
cosmovisor init <path/to/fuelsequencerd>
At this point cosmovisor run will be the equivalent of running fuelsequencerd, however you should not run the node for now.
Configure State Sync
State Sync allows a node to get synced up quickly.
To configure State Sync, you will need to set these values in ~/.fuelsequencer/config/config.toml under [statesync]:
enable = trueto enable State Syncrpc_servers = ...trust_height = ...trust_hash = ...
The last three values can be obtained from the explorer.
You will need to specify at least two comma-separated RPC servers in rpc_servers. You can either refer to the list of alternate RPC servers above or use the same one twice.
Run the Sidecar
At this point you should already be able to run fuelsequencerd start-sidecar with the right flags, to run the Sidecar. However, it is highly recommended to run the Sidecar as a background service.
It is also very important to ensure that you provide all the necessary flags when running the Sidecar to ensure that it can connect to an Ethereum node and to the Sequencer node, and is also accessible by the Sequencer node. The most important flags are:
host: host for the gRPC server to listen onport: port for the gRPC server to listen oneth_ws_url: Ethereum node WebSocket endpointeth_rpc_url: Ethereum node RPC endpointeth_contract_address: address in hex format of the contract to monitor for logs. This MUST be set to0xBa0e6bF94580D49B5Aaaa54279198D424B23eCC3.sequencer_grpc_url: Sequencer node gRPC endpoint
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sidecar
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=<HOME>/go/bin/fuelsequencerd start-sidecar \
--host "0.0.0.0" \
--sequencer_grpc_url "127.0.0.1:9090" \
--eth_ws_url "<ETHEREUM_NODE_WS>" \
--eth_rpc_url "<ETHEREUM_NODE_RPC>" \
--eth_contract_address "0xBa0e6bF94580D49B5Aaaa54279198D424B23eCC3"
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sidecar</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/fuelsequencerd</string>
<string>start-sidecar</string>
<string>--host</string>
<string>0.0.0.0</string>
<string>--sequencer_grpc_url</string>
<string>127.0.0.1:9090</string>
<string>--eth_ws_url</string>
<string>[ETHEREUM_NODE_WS]</string>
<string>--eth_rpc_url</string>
<string>[ETHEREUM_NODE_RPC]</string>
<string>--eth_contract_address</string>
<string>0xBa0e6bF94580D49B5Aaaa54279198D424B23eCC3</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sidecar.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sidecar.err</string>
</dict>
</plist>
Run the Sequencer
At this point you should already be able to run cosmovisor run start to run the Sequencer. However, it is highly recommended to run the Sequencer as a background service.
Some examples are provided below for Linux and Mac. You will need to replicate the environment variables defined when setting up Cosmovisor.
Linux
On Linux, you can use systemd to run the Sequencer in the background. Knowledge of how to use systemd is assumed here.
Here's an example service file with some placeholder (<...>) values that must be filled-in:
Click me...
[Unit]
Description=Sequencer Node
After=network.target
[Service]
Type=simple
User=<USER>
ExecStart=/home/<USER>/go/bin/cosmovisor run start
Restart=on-failure
RestartSec=3
LimitNOFILE=4096
Environment="DAEMON_NAME=fuelsequencerd"
Environment="DAEMON_HOME=/home/<USER>/.fuelsequencer"
Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=true"
Environment="DAEMON_LOG_BUFFER_SIZE=512"
Environment="DAEMON_RESTART_AFTER_UPGRADE=true"
Environment="UNSAFE_SKIP_BACKUP=true"
Environment="DAEMON_SHUTDOWN_GRACE=15s"
[Install]
WantedBy=multi-user.target
Mac
On Mac, you can use launchd to run the Sequencer in the background. Knowledge of how to use launchd is assumed here.
Here's an example plist file with some placeholder ([...]) values that must be filled-in:
Click me...
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>fuel.sequencer</string>
<key>ProgramArguments</key>
<array>
<string>/Users/[User]/go/bin/cosmovisor</string>
<string>run</string>
<string>start</string>
</array>
<key>UserName</key>
<string>[User]</string>
<key>EnvironmentVariables</key>
<dict>
<key>DAEMON_NAME</key>
<string>fuelsequencerd</string>
<key>DAEMON_HOME</key>
<string>/Users/[User]/.fuelsequencer</string>
<key>DAEMON_ALLOW_DOWNLOAD_BINARIES</key>
<string>true</string>
<key>DAEMON_LOG_BUFFER_SIZE</key>
<string>512</string>
<key>DAEMON_RESTART_AFTER_UPGRADE</key>
<string>true</string>
<key>UNSAFE_SKIP_BACKUP</key>
<string>true</string>
<key>DAEMON_SHUTDOWN_GRACE</key>
<string>15s</string>
</dict>
<key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key>
<false/>
</dict>
<key>HardResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>4096</integer>
</dict>
<key>StandardOutPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.out</string>
<key>StandardErrorPath</key>
<string>/Users/[User]/Library/Logs/fuel-sequencer.err</string>
</dict>
</plist>
Creating an Account
To run a validator, you will need to have a Sequencer account address. Generate an address with a key name:
fuelsequencerd keys add <NAME> # for a brand new key
# or
fuelsequencerd keys add <NAME> --recover # to create from a mnemonic
This will give you an output with an address (e.g. fuelsequencer1l7qk9umswg65av0zygyymgx5yg0fx4g0dpp2tl) and a private mnemonic, if you generated a brand new key. Store the mnemonic safely.
Fuel Sequencer addresses also have an Ethereum-compatible (i.e. hex) format. To generate the hex address corresponding to your Sequencer address, run the following:
fuelsequencerd keys parse <ADDRESS>
This will give an output in this form:
bytes: FF8162F37072354EB1E222084DA0D4221E93550F
human: fuelsequencer
Adding the 0x prefix to the address in the first line gives you your Ethereum-compatible address, used to deposit into and interact with your Sequencer address from Ethereum. In this case, it's 0xFF8162F37072354EB1E222084DA0D4221E93550F.
Funding the Account
Ensure your mainnet Ethereum account (EOA) has sufficient ETH to cover gas fees and FUEL tokens to transfer to your Sequencer account.
Important Addresses
- FUEL Token:
0x675B68AA4d9c2d3BB3F0397048e62E6B7192079c - Sequencer Interface (Bridge):
0xca0c6B264f0F9958Ec186eb2EAa208966187D866
Token Approval
Before proceeding, you must approve the Fuel token contract to allow the transfer of tokens.
In the Fuel Token Etherscan contract UI, use the approve (0x095ea7b3) function:
-
Spender (
address): Set this to the Sequencer Interface (Bridge) address:0xca0c6B264f0F9958Ec186eb2EAa208966187D866. -
Value (
uint256): Enter the number of tokens to approve, including 9 additional decimal places. For unlimited approval, use:0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

Bridging Tokens
To bridge tokens, connect your Ethereum wallet by clicking "Connect to Web3" in the top left. Then, use the depositFor (0x36efd6f) function to fund your sequencer account.
Transfer your FUEL tokens using the Sequencer Interface (Bridge)Etherscan UI.

- Amount (
uint256): Enter the number of tokens to send, including 9 additional decimal places. - Recipient address: Enter the Ethereum-compatible address you generated earlier (e.g.,
0xFF8162F37072354EB1E222084DA0D4221E93550F).
Click "Write" to confirm the transaction. The transfer may take ~20 minutes to process.
Verifying Funds
To verify your funds, enter your sequencer account address (i.e. fuelsequencer1l7qk9umswg65av0zygyymgx5yg0fx4g0dpp2tl) in the mainnet block explorer.

⚠ WARNING: Always test with a small transfer before bridging FUEL tokens.
Withdrawals
Withdrawals can be easily initiated through the CLI and will be settled on Ethereum approximately 3 days later, as the commitment and bridge finalizations must be completed first.
Identify the account from which you wish to withdraw. Use the following command to list all previously created account names matching your account address above:
fuelsequencerd keys list
Example output:
address: fuelsequencer1zzu4804kp6m6whzza6r75g7mnme2ahqkjuw4kf
name: my-mainnet-validator
pubkey: '{"@type":"/cosmos.crypto.secp256k1.PubKey","key":"Al6W+Ttrscm/8njeMOt79T0BOdphfWGXrDLij+O3g19N"}'
type: local
Verify that this is the correct address and account name from which you wish to withdraw.
To initiate the withdrawal, use the following command where <eth-destination-address> is any Ethereum address you wish to withdraw to and <amount-in-fuel> is the amount of FUEL you wish to withdraw:
Note: The amount in FUEL must include 9 decimal places.
fuelsequencerd tx bridge withdraw-to-ethereum <eth-destination-address> <amount-in-fuel> \
--from=<key> \
--gas-prices=10fuel \
--gas=auto \
--gas-adjustment 1.5 \
--node="https://fuel-rpc.polkachu.com/" \
--chain-id="seq-mainnet-1"
For example:
fuelsequencerd tx bridge withdraw-to-ethereum 0xd70080dE4535db4A64798a23619Db64fB28fD079 1fuel \
--from=my-mainnet-validator \
--gas-prices=10fuel \
--gas=auto \
--gas-adjustment 1.5 \
--node="https://fuel-rpc.polkachu.com/" \
--chain-id="seq-mainnet-1"
Review the transaction details and confirm the transaction by typing yes when prompted:
gas estimate: 106942
auth_info:
fee:
amount:
- amount: "1069420"
denom: fuel
gas_limit: "106942"
granter: ""
payer: ""
signer_infos: []
tip: null
body:
extension_options: []
memo: ""
messages:
- '@type': /fuelsequencer.bridge.v1.MsgWithdrawToEthereum
amount:
amount: "1"
denom: fuel
from: fuelsequencer1zzu4804kp6m6whzza6r75g7mnme2ahqkjuw4kf
to: 0xd70080dE4535db4A64798a23619Db64fB28fD079
non_critical_extension_options: []
timeout_height: "0"
signatures: []
confirm transaction before signing and broadcasting [y/N]:
If the transaction is successful, you will receive a transaction hash, which you can paste and monitor the status of your withdrawal here:
code: 0
codespace: ""
data: ""
events: []
gas_used: "0"
gas_wanted: "0"
height: "0"
info: ""
logs: []
raw_log: ""
timestamp: ""
tx: null
txhash: AD541CE1DCDBD8638C5DFD3C7AF3A3AAF8B9CD0AF265C3AFD96633CE8FAF4CF4

After verifying your withdrawal on the shared sequencer explorer, visit Simply Staking and connect your wallet. Navigate to the Withdrawal tab on the right to monitor the progress of your withdrawal.

Once the 3-day waiting period has passed, the withdrawal will require manual action to pull the funds out.
Create the Validator
To create the validator, a prerequisite is to have at least 1FUEL, with enough extra to pay for gas fees. You can check your balance from the explorer.
Once you have FUEL tokens, run the following to create a validator, using the name of the account that you created in the previous steps:
fuelsequencerd tx staking create-validator path/to/validator.json \
--from <NAME> \
--gas auto \
--gas-prices 10fuel \
--gas-adjustment 1.5 \
--chain-id seq-mainnet-1
...where validator.json contains:
{
"pubkey": {"@type":"/cosmos.crypto.ed25519.PubKey","key":"<PUBKEY>"},
"amount": "1000000000fuel",
"moniker": "<MONIKER>",
"identity": "<OPTIONAL-IDENTITY>",
"website": "<OPTIONAL-WEBSITE>",
"security": "<OPTIONAL-EMAIL>",
"details": "<OPTIONAL-DETAILS>",
"commission-rate": "0.05",
"commission-max-rate": "<MAX-RATE>",
"commission-max-change-rate": "<MAX-CHANGE-RATE>",
"min-self-delegation": "1"
}
...where the pubkey can be obtained using fuelsequencerd tendermint show-validator.
What to Expect
- The Sequencer should show block syncing.
- The Sidecar should show block extraction. Occasionally it also receives requests for events.
Tendermint KMS
If you will be using tmkms, make sure that in the config:
- Chain ID is set to
seq-mainnet-1wherever applicable account_key_prefix = "fuelsequencerpub"consensus_key_prefix = "fuelsequencervalconspub"sign_extensions = trueprotocol_version = "v0.34"
Additional Advanced Configuration
Sidecar flags:
development: starts the sidecar in development mode.eth_max_block_range: max number of Ethereum blocks queried at one go.eth_min_logs_query_interval: minimum wait between successive queries for logs.unsafe_eth_start_block: the Ethereum block to start querying from.unsafe_eth_end_block: the last Ethereum block to query. Incorrect use can cause the validator to propose empty blocks, leading to slashing!sequencer_path_to_cert_file: path to the certificate file of the Sequencer infrastructure for secure communication. Specify this value if the Sequencer infrastructure was set up using TLS.sidecar_path_to_cert_file: path to the certificate file of the sidecar server for secure communication. Specify this value if you want to set up a sidecar server with TLS.sidecar_path_to_key_file: path to the private key file of the sidecar server for secure communication. Specify this value if you want to set up a sidecar server with TLS.prometheus_enabled: enables serving of prometheus metrics.prometheus_listen_address: address to listen for prometheus collectors (default ":8081").prometheus_max_open_connections: max number of simultaneous connections (default 3).prometheus_namespace: instrumentation namespace (default "sidecar").prometheus_read_header_timeout: amount of time allowed to read request headers (default 10s).prometheus_write_timeout: maximum duration before timing out writes of the response (default 10s).
Sidecar client flags:
sidecar_grpc_url: the sidecar's gRPC endpoint.query_timeout: how long to wait before the request times out.
References
Based on material from:
- https://docs.cosmos.network/main/tooling/cosmovisor
- https://docs.osmosis.zone/overview/validate/joining-mainnet#set-up-cosmovisor
Getting Started
Installation Guide
Please visit the Fuel installation guide to install the Fuel toolchain binaries and prerequisites.
forc is Sway equivalent of Rust's cargo. fuel-core is a Fuel full node implementation.
There are two main ways you can use the Fuel Rust SDK:
- Creating a new Sway project with
forcand running the tests - Creating a standalone project and importing the
fuels-rscrate
Creating a new project with Forc
You can create a new Sway project with
forc new <Project name>
Or you can initialize a project within an existing folder with
forc init
Adding a Rust integration test to the Sway project
Now that we have a new project, we can add a Rust integration test using a cargo generate template.
If cargo generate is not already installed, you can install it with:
cargo install cargo-generate
Note You can learn more about cargo generate by visiting its repository.
Let's generate the default test harness with the following command:
cargo generate --init fuellabs/sway templates/sway-test-rs --name <Project name> --force
--force forces your --name input to retain your desired casing for the {{project-name}} placeholder in the template. Otherwise, cargo-generate automatically converts it to kebab-case. With --force, this means that both my_fuel_project and my-fuel-project are valid project names, depending on your needs.
Before running test, we need to build the Sway project with:
forc build
Afterwards, we can run the test with:
cargo test
Note If you need to capture output from the tests, use one of the following commands:
cargo test -- --nocapture
Importing the Fuel Rust SDK
Add these dependencies on your Cargo.toml:
fuels = "0.66.0"
Note We're using version
0.66.0of the SDK, which is the latest version at the time of this writing.
And then, in your Rust file that's going to make use of the SDK:
use fuels::prelude::*;
The Fuel Rust SDK source code
Another way to experience the SDK is to look at the source code. The e2e/tests/ folder is full of integration tests that go through almost all aspects of the SDK.
Note Before running the tests, we need to build all the Sway test projects. The file
packages/fuels/Forc.tomlcontains a `[workspace], which members are the paths to all integration tests. To build these tests, run the following command:
forc build --release --path e2e
forccan also be used to clean and format the test projects. Check thehelpoutput for more info.
After building the projects, we can run the tests with
cargo test
If you need all targets and all features, you can run
cargo test --all-targets --all-features
Note If you need to capture output from the tests, you can run
cargo test -- --nocapture
More in-depth Fuel and Sway knowledge
Read The Sway Book for more in-depth knowledge about Sway, the official smart contract language for the Fuel Virtual Machine.
Connecting to a Fuel node
At a high level, you can use the Fuel Rust SDK to build Rust-based applications that can run computations on the Fuel Virtual Machine through interactions with smart contracts written in Sway.
For this interaction to work, the SDK must be able to communicate with a fuel-core node; you have two options at your disposal:
- Use the testnet or run a Fuel node (using
fuel-core) and instantiate a provider that points to that node's IP and port. - Use the SDK's native
launch_provider_and_get_wallet()that runs a short-lived test Fuel node;
The second option is ideal for smart contract testing, as you can quickly spin up and tear down nodes between specific test cases.
For application building, you should use the first option.
Connecting to the Testnet or an external node
We can interact with the Testnet node by using the following example.
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
For detailed information about various testnet networks and their optimal toolchain configurations for your project, please visit the following link:
In the code example, we connected a new provider to the Testnet node and created a new wallet from a private key.
Note: New wallets on the Testnet will not have any assets! They can be obtained by providing the wallet address to the faucet at
Once the assets have been transferred to the wallet, you can reuse it in other tests by providing the private key!
In addition to the faucet, there is a block explorer for the Testnet at
If you want to connect to another node just change the URL or IP and port. For example, to connect to a local node that was created with fuel-core you can use:
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Running a short-lived Fuel node with the SDK
You can use the SDK to spin up a local, ideally short-lived Fuel node. Then, you can instantiate a Fuel client, pointing to this node.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
This approach is ideal for contract testing.
You can also use the test helper setup_test_provider() for this:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
You can also use launch_provider_and_get_wallet(), which abstracts away the setup_test_provider() and the wallet creation, all in one single method:
let wallet = launch_provider_and_get_wallet().await?;
Features
Fuel-core lib
The fuel-core-lib feature allows us to run a fuel-core node without installing the fuel-core binary on the local machine. Using the fuel-core-lib feature flag entails downloading all the dependencies needed to run the fuel-core node.
fuels = { version = "0.70.1", features = ["fuel-core-lib"] }
RocksDB
The rocksdb is an additional feature that, when combined with fuel-core-lib, provides persistent storage capabilities while using fuel-core as a library.
fuels = { version = "0.70.1", features = ["rocksdb"] }
RocksDB
RocksDB enables the preservation of the blockchain's state locally, facilitating its future utilization.
To create or use a local database, follow these instructions:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Note: If the specified database does not exist, a new database will be created at that path. To utilize the code snippets above, either the
fuel-corebinary must be present, or both thefuel-core-libandrocksdbfeatures need to be enabled.
Querying the blockchain
Once you set up a provider, you can interact with the Fuel blockchain. Here are a few examples of what you can do with a provider; for a more in-depth overview of the API, check the official provider API documentation.
- Set up
- Get all coins from an address
- Get spendable resources owned by an address
- Get balances from an address
Set up
You might need to set up a test blockchain first. You can skip this step if you're connecting to an external blockchain.
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Get all coins from an address
This method returns all unspent coins (of a given asset ID) from a wallet.
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Get spendable resources owned by an address
The following example shows how to fetch resources owned by an address. First, you create a ResourceFilter which specifies the target address, asset ID, and amount. You can also define UTXO IDs and message IDs that should be excluded when retrieving the resources:
#[cfg(feature = "coin-cache")]
use std::sync::Arc;
use std::{collections::HashMap, fmt::Debug, net::SocketAddr};
mod cache;
mod retry_util;
mod retryable_client;
mod supported_fuel_core_version;
mod supported_versions;
use crate::provider::cache::CacheableRpcs;
pub use cache::TtlConfig;
use cache::{CachedClient, SystemClock};
use chrono::{DateTime, Utc};
use fuel_core_client::client::{
pagination::{PageDirection, PaginatedResult, PaginationRequest},
types::{
balance::Balance,
contract::ContractBalance,
gas_price::{EstimateGasPrice, LatestGasPrice},
},
};
use fuel_core_types::services::executor::TransactionExecutionResult;
use fuel_tx::{
AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId,
};
use fuel_types::{Address, BlockHeight, Bytes32, Nonce};
#[cfg(feature = "coin-cache")]
use fuels_core::types::coin_type_id::CoinTypeId;
use fuels_core::{
constants::{DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE},
types::{
bech32::{Bech32Address, Bech32ContractId},
block::{Block, Header},
chain_info::ChainInfo,
coin::Coin,
coin_type::CoinType,
errors::Result,
message::Message,
message_proof::MessageProof,
node_info::NodeInfo,
transaction::{Transaction, Transactions},
transaction_builders::{Blob, BlobId},
transaction_response::TransactionResponse,
tx_status::TxStatus,
DryRun, DryRunner,
},
};
pub use retry_util::{Backoff, RetryConfig};
pub use supported_fuel_core_version::SUPPORTED_FUEL_CORE_VERSION;
use tai64::Tai64;
#[cfg(feature = "coin-cache")]
use tokio::sync::Mutex;
#[cfg(feature = "coin-cache")]
use crate::coin_cache::CoinsCache;
use crate::provider::retryable_client::RetryableClient;
const NUM_RESULTS_PER_REQUEST: i32 = 100;
#[derive(Debug, Clone, PartialEq)]
// ANCHOR: transaction_cost
pub struct TransactionCost {
pub gas_price: u64,
pub gas_used: u64,
pub metered_bytes_size: u64,
pub total_fee: u64,
}
// ANCHOR_END: transaction_cost
pub(crate) struct ResourceQueries {
utxos: Vec<UtxoId>,
messages: Vec<Nonce>,
asset_id: Option<AssetId>,
amount: u64,
}
impl ResourceQueries {
pub fn exclusion_query(&self) -> Option<(Vec<UtxoId>, Vec<Nonce>)> {
if self.utxos.is_empty() && self.messages.is_empty() {
return None;
}
Some((self.utxos.clone(), self.messages.clone()))
}
pub fn spend_query(&self, base_asset_id: AssetId) -> Vec<(AssetId, u64, Option<u32>)> {
vec![(self.asset_id.unwrap_or(base_asset_id), self.amount, None)]
}
}
#[derive(Default)]
// ANCHOR: resource_filter
pub struct ResourceFilter {
pub from: Bech32Address,
pub asset_id: Option<AssetId>,
pub amount: u64,
pub excluded_utxos: Vec<UtxoId>,
pub excluded_message_nonces: Vec<Nonce>,
}
// ANCHOR_END: resource_filter
impl ResourceFilter {
pub fn owner(&self) -> Address {
(&self.from).into()
}
pub(crate) fn resource_queries(&self) -> ResourceQueries {
ResourceQueries {
utxos: self.excluded_utxos.clone(),
messages: self.excluded_message_nonces.clone(),
asset_id: self.asset_id,
amount: self.amount,
}
}
}
/// Encapsulates common client operations in the SDK.
/// Note that you may also use `client`, which is an instance
/// of `FuelClient`, directly, which provides a broader API.
#[derive(Debug, Clone)]
pub struct Provider {
cached_client: CachedClient<RetryableClient>,
#[cfg(feature = "coin-cache")]
coins_cache: Arc<Mutex<CoinsCache>>,
}
impl Provider {
pub async fn from(addr: impl Into<SocketAddr>) -> Result<Self> {
let addr = addr.into();
Self::connect(format!("http://{addr}")).await
}
pub fn set_cache_ttl(&mut self, ttl: TtlConfig) {
self.cached_client.set_ttl(ttl);
}
pub async fn clear_cache(&self) {
self.cached_client.clear().await;
}
pub async fn healthy(&self) -> Result<bool> {
Ok(self.uncached_client().health().await?)
}
/// Connects to an existing node at the given address.
pub async fn connect(url: impl AsRef<str>) -> Result<Provider> {
let client = CachedClient::new(
RetryableClient::connect(&url, Default::default()).await?,
TtlConfig::default(),
SystemClock,
);
Ok(Self {
cached_client: client,
#[cfg(feature = "coin-cache")]
coins_cache: Default::default(),
})
}
pub fn url(&self) -> &str {
self.uncached_client().url()
}
pub async fn blob(&self, blob_id: BlobId) -> Result<Option<Blob>> {
Ok(self
.uncached_client()
.blob(blob_id.into())
.await?
.map(|blob| Blob::new(blob.bytecode)))
}
pub async fn blob_exists(&self, blob_id: BlobId) -> Result<bool> {
Ok(self.uncached_client().blob_exists(blob_id.into()).await?)
}
/// Sends a transaction to the underlying Provider's client.
pub async fn send_transaction_and_await_commit<T: Transaction>(
&self,
tx: T,
) -> Result<TxStatus> {
#[cfg(feature = "coin-cache")]
let base_asset_id = *self.consensus_parameters().await?.base_asset_id();
#[cfg(feature = "coin-cache")]
self.check_inputs_already_in_cache(&tx.used_coins(&base_asset_id))
.await?;
let tx = self.prepare_transaction_for_sending(tx).await?;
let tx_status = self
.uncached_client()
.submit_and_await_commit(&tx.clone().into())
.await?
.into();
#[cfg(feature = "coin-cache")]
if matches!(
tx_status,
TxStatus::SqueezedOut { .. } | TxStatus::Revert { .. }
) {
self.coins_cache
.lock()
.await
.remove_items(tx.used_coins(&base_asset_id))
}
Ok(tx_status)
}
async fn prepare_transaction_for_sending<T: Transaction>(&self, mut tx: T) -> Result<T> {
let consensus_parameters = self.consensus_parameters().await?;
tx.precompute(&consensus_parameters.chain_id())?;
let chain_info = self.chain_info().await?;
let Header {
height: latest_block_height,
state_transition_bytecode_version: latest_chain_executor_version,
..
} = chain_info.latest_block.header;
if tx.is_using_predicates() {
tx.estimate_predicates(self, Some(latest_chain_executor_version))
.await?;
tx.clone()
.validate_predicates(&consensus_parameters, latest_block_height)?;
}
self.validate_transaction(tx.clone()).await?;
Ok(tx)
}
pub async fn send_transaction<T: Transaction>(&self, tx: T) -> Result<TxId> {
let tx = self.prepare_transaction_for_sending(tx).await?;
self.submit(tx).await
}
pub async fn await_transaction_commit<T: Transaction>(&self, id: TxId) -> Result<TxStatus> {
Ok(self
.uncached_client()
.await_transaction_commit(&id)
.await?
.into())
}
async fn validate_transaction<T: Transaction>(&self, tx: T) -> Result<()> {
let tolerance = 0.0;
let TransactionCost { gas_used, .. } = self
.estimate_transaction_cost(tx.clone(), Some(tolerance), None)
.await?;
tx.validate_gas(gas_used)?;
Ok(())
}
#[cfg(not(feature = "coin-cache"))]
async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
Ok(self.uncached_client().submit(&tx.into()).await?)
}
#[cfg(feature = "coin-cache")]
async fn find_in_cache<'a>(
&self,
coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> Option<((Bech32Address, AssetId), CoinTypeId)> {
let mut locked_cache = self.coins_cache.lock().await;
for (key, ids) in coin_ids {
let items = locked_cache.get_active(key);
if items.is_empty() {
continue;
}
for id in ids {
if items.contains(id) {
return Some((key.clone(), id.clone()));
}
}
}
None
}
#[cfg(feature = "coin-cache")]
async fn check_inputs_already_in_cache<'a>(
&self,
coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> Result<()> {
use fuels_core::types::errors::{transaction, Error};
if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await {
let msg = match coin_type_id {
CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"),
CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"),
};
Err(Error::Transaction(transaction::Reason::Validation(
format!("{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`"),
)))
} else {
Ok(())
}
}
#[cfg(feature = "coin-cache")]
async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
let consensus_parameters = self.consensus_parameters().await?;
let base_asset_id = consensus_parameters.base_asset_id();
let used_utxos = tx.used_coins(base_asset_id);
self.check_inputs_already_in_cache(&used_utxos).await?;
let tx_id = self.uncached_client().submit(&tx.into()).await?;
self.coins_cache.lock().await.insert_multiple(used_utxos);
Ok(tx_id)
}
pub async fn tx_status(&self, tx_id: &TxId) -> Result<TxStatus> {
Ok(self
.uncached_client()
.transaction_status(tx_id)
.await?
.into())
}
pub async fn chain_info(&self) -> Result<ChainInfo> {
Ok(self.uncached_client().chain_info().await?.into())
}
pub async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
self.cached_client.consensus_parameters().await
}
pub async fn node_info(&self) -> Result<NodeInfo> {
Ok(self.uncached_client().node_info().await?.into())
}
pub async fn latest_gas_price(&self) -> Result<LatestGasPrice> {
Ok(self.uncached_client().latest_gas_price().await?)
}
pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result<EstimateGasPrice> {
Ok(self
.uncached_client()
.estimate_gas_price(block_horizon)
.await?)
}
pub async fn dry_run(&self, tx: impl Transaction) -> Result<TxStatus> {
let [tx_status] = self
.uncached_client()
.dry_run(Transactions::new().insert(tx).as_slice())
.await?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.try_into()
.expect("should have only one element");
Ok(tx_status)
}
pub async fn dry_run_multiple(
&self,
transactions: Transactions,
) -> Result<Vec<(TxId, TxStatus)>> {
Ok(self
.uncached_client()
.dry_run(transactions.as_slice())
.await?
.into_iter()
.map(|execution_status| (execution_status.id, execution_status.into()))
.collect())
}
pub async fn dry_run_opt(
&self,
tx: impl Transaction,
utxo_validation: bool,
gas_price: Option<u64>,
) -> Result<TxStatus> {
let [tx_status] = self
.uncached_client()
.dry_run_opt(
Transactions::new().insert(tx).as_slice(),
Some(utxo_validation),
gas_price,
)
.await?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.try_into()
.expect("should have only one element");
Ok(tx_status)
}
pub async fn dry_run_opt_multiple(
&self,
transactions: Transactions,
utxo_validation: bool,
gas_price: Option<u64>,
) -> Result<Vec<(TxId, TxStatus)>> {
Ok(self
.uncached_client()
.dry_run_opt(transactions.as_slice(), Some(utxo_validation), gas_price)
.await?
.into_iter()
.map(|execution_status| (execution_status.id, execution_status.into()))
.collect())
}
/// Gets all unspent coins owned by address `from`, with asset ID `asset_id`.
pub async fn get_coins(&self, from: &Bech32Address, asset_id: AssetId) -> Result<Vec<Coin>> {
let mut coins: Vec<Coin> = vec![];
let mut cursor = None;
loop {
let response = self
.uncached_client()
.coins(
&from.into(),
Some(&asset_id),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
coins.extend(response.results.into_iter().map(Into::into));
cursor = response.cursor;
}
Ok(coins)
}
async fn request_coins_to_spend(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
let queries = filter.resource_queries();
let consensus_parameters = self.consensus_parameters().await?;
let base_asset_id = *consensus_parameters.base_asset_id();
let res = self
.uncached_client()
.coins_to_spend(
&filter.owner(),
queries.spend_query(base_asset_id),
queries.exclusion_query(),
)
.await?
.into_iter()
.flatten()
.map(CoinType::from)
.collect();
Ok(res)
}
/// Get some spendable coins of asset `asset_id` for address `from` that add up at least to
/// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number
/// of coins (UXTOs) is optimized to prevent dust accumulation.
#[cfg(not(feature = "coin-cache"))]
pub async fn get_spendable_resources(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
self.request_coins_to_spend(filter).await
}
/// Get some spendable coins of asset `asset_id` for address `from` that add up at least to
/// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number
/// of coins (UXTOs) is optimized to prevent dust accumulation.
/// Coins that were recently submitted inside a tx will be ignored from the results.
#[cfg(feature = "coin-cache")]
pub async fn get_spendable_resources(
&self,
mut filter: ResourceFilter,
) -> Result<Vec<CoinType>> {
self.extend_filter_with_cached(&mut filter).await?;
self.request_coins_to_spend(filter).await
}
#[cfg(feature = "coin-cache")]
async fn extend_filter_with_cached(&self, filter: &mut ResourceFilter) -> Result<()> {
let consensus_parameters = self.consensus_parameters().await?;
let mut cache = self.coins_cache.lock().await;
let asset_id = filter
.asset_id
.unwrap_or(*consensus_parameters.base_asset_id());
let used_coins = cache.get_active(&(filter.from.clone(), asset_id));
let excluded_utxos = used_coins
.iter()
.filter_map(|coin_id| match coin_id {
CoinTypeId::UtxoId(utxo_id) => Some(utxo_id),
_ => None,
})
.cloned()
.collect::<Vec<_>>();
let excluded_message_nonces = used_coins
.iter()
.filter_map(|coin_id| match coin_id {
CoinTypeId::Nonce(nonce) => Some(nonce),
_ => None,
})
.cloned()
.collect::<Vec<_>>();
filter.excluded_utxos.extend(excluded_utxos);
filter
.excluded_message_nonces
.extend(excluded_message_nonces);
Ok(())
}
/// Get the balance of all spendable coins `asset_id` for address `address`. This is different
/// from getting coins because we are just returning a number (the sum of UTXOs amount) instead
/// of the UTXOs.
pub async fn get_asset_balance(
&self,
address: &Bech32Address,
asset_id: AssetId,
) -> Result<u64> {
Ok(self
.uncached_client()
.balance(&address.into(), Some(&asset_id))
.await?)
}
/// Get the balance of all spendable coins `asset_id` for contract with id `contract_id`.
pub async fn get_contract_asset_balance(
&self,
contract_id: &Bech32ContractId,
asset_id: AssetId,
) -> Result<u64> {
Ok(self
.uncached_client()
.contract_balance(&contract_id.into(), Some(&asset_id))
.await?)
}
/// Get all the spendable balances of all assets for address `address`. This is different from
/// getting the coins because we are only returning the numbers (the sum of UTXOs coins amount
/// for each asset id) and not the UTXOs coins themselves
pub async fn get_balances(&self, address: &Bech32Address) -> Result<HashMap<String, u128>> {
let mut balances = HashMap::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.balances(
&address.into(),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
balances.extend(response.results.into_iter().map(
|Balance {
owner: _,
amount,
asset_id,
}| (asset_id.to_string(), amount),
));
cursor = response.cursor;
}
Ok(balances)
}
/// Get all balances of all assets for the contract with id `contract_id`.
pub async fn get_contract_balances(
&self,
contract_id: &Bech32ContractId,
) -> Result<HashMap<AssetId, u64>> {
let mut contract_balances = HashMap::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.contract_balances(
&contract_id.into(),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
contract_balances.extend(response.results.into_iter().map(
|ContractBalance {
contract: _,
amount,
asset_id,
}| (asset_id, amount),
));
cursor = response.cursor;
}
Ok(contract_balances)
}
pub async fn get_transaction_by_id(&self, tx_id: &TxId) -> Result<Option<TransactionResponse>> {
Ok(self
.uncached_client()
.transaction(tx_id)
.await?
.map(Into::into))
}
pub async fn get_transactions(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
let pr = self.uncached_client().transactions(request).await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
// Get transaction(s) by owner
pub async fn get_transactions_by_owner(
&self,
owner: &Bech32Address,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
let pr = self
.uncached_client()
.transactions_by_owner(&owner.into(), request)
.await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
pub async fn latest_block_height(&self) -> Result<u32> {
Ok(self.chain_info().await?.latest_block.header.height)
}
pub async fn latest_block_time(&self) -> Result<Option<DateTime<Utc>>> {
Ok(self.chain_info().await?.latest_block.header.time)
}
pub async fn produce_blocks(
&self,
blocks_to_produce: u32,
start_time: Option<DateTime<Utc>>,
) -> Result<u32> {
let start_time = start_time.map(|time| Tai64::from_unix(time.timestamp()).0);
Ok(self
.uncached_client()
.produce_blocks(blocks_to_produce, start_time)
.await?
.into())
}
pub async fn block(&self, block_id: &Bytes32) -> Result<Option<Block>> {
Ok(self
.uncached_client()
.block(block_id)
.await?
.map(Into::into))
}
pub async fn block_by_height(&self, height: BlockHeight) -> Result<Option<Block>> {
Ok(self
.uncached_client()
.block_by_height(height)
.await?
.map(Into::into))
}
// - Get block(s)
pub async fn get_blocks(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<Block, String>> {
let pr = self.uncached_client().blocks(request).await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
pub async fn estimate_transaction_cost<T: Transaction>(
&self,
mut tx: T,
tolerance: Option<f64>,
block_horizon: Option<u32>,
) -> Result<TransactionCost> {
let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON);
let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE);
let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?;
let gas_used = self
.get_gas_used_with_tolerance(tx.clone(), tolerance)
.await?;
if tx.is_using_predicates() {
tx.estimate_predicates(self, None).await?;
}
let transaction_fee = tx
.clone()
.fee_checked_from_tx(&self.consensus_parameters().await?, gas_price)
.expect("Error calculating TransactionFee");
Ok(TransactionCost {
gas_price,
gas_used,
metered_bytes_size: tx.metered_bytes_size() as u64,
total_fee: transaction_fee.max_fee(),
})
}
// Increase estimated gas by the provided tolerance
async fn get_gas_used_with_tolerance<T: Transaction>(
&self,
tx: T,
tolerance: f64,
) -> Result<u64> {
let receipts = self.dry_run_opt(tx, false, None).await?.take_receipts();
let gas_used = self.get_script_gas_used(&receipts);
Ok((gas_used as f64 * (1.0 + tolerance)).ceil() as u64)
}
fn get_script_gas_used(&self, receipts: &[Receipt]) -> u64 {
receipts
.iter()
.rfind(|r| matches!(r, Receipt::ScriptResult { .. }))
.map(|script_result| {
script_result
.gas_used()
.expect("could not retrieve gas used from ScriptResult")
})
.unwrap_or(0)
}
pub async fn get_messages(&self, from: &Bech32Address) -> Result<Vec<Message>> {
let mut messages = Vec::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.messages(
Some(&from.into()),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
messages.extend(response.results.into_iter().map(Into::into));
cursor = response.cursor;
}
Ok(messages)
}
pub async fn get_message_proof(
&self,
tx_id: &TxId,
nonce: &Nonce,
commit_block_id: Option<&Bytes32>,
commit_block_height: Option<u32>,
) -> Result<MessageProof> {
self.uncached_client()
.message_proof(
tx_id,
nonce,
commit_block_id.map(Into::into),
commit_block_height.map(Into::into),
)
.await
.map(Into::into)
.map_err(Into::into)
}
pub async fn is_user_account(&self, address: impl Into<Bytes32>) -> Result<bool> {
self.uncached_client()
.is_user_account(*address.into())
.await
}
pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
self.uncached_client_mut().set_retry_config(retry_config);
self
}
pub async fn contract_exists(&self, contract_id: &Bech32ContractId) -> Result<bool> {
Ok(self
.uncached_client()
.contract_exists(&contract_id.into())
.await?)
}
fn uncached_client(&self) -> &RetryableClient {
self.cached_client.inner()
}
fn uncached_client_mut(&mut self) -> &mut RetryableClient {
self.cached_client.inner_mut()
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DryRunner for Provider {
async fn dry_run(&self, tx: FuelTransaction) -> Result<DryRun> {
let [tx_execution_status] = self
.uncached_client()
.dry_run_opt(&vec![tx], Some(false), Some(0))
.await?
.try_into()
.expect("should have only one element");
let receipts = tx_execution_status.result.receipts();
let script_gas = self.get_script_gas_used(receipts);
let variable_outputs = receipts
.iter()
.filter(
|receipt| matches!(receipt, Receipt::TransferOut { amount, .. } if *amount != 0),
)
.count();
let succeeded = matches!(
tx_execution_status.result,
TransactionExecutionResult::Success { .. }
);
let dry_run = DryRun {
succeeded,
script_gas,
variable_outputs,
};
Ok(dry_run)
}
async fn estimate_gas_price(&self, block_horizon: u32) -> Result<u64> {
Ok(self.estimate_gas_price(block_horizon).await?.gas_price)
}
async fn estimate_predicates(
&self,
tx: &FuelTransaction,
_latest_chain_executor_version: Option<u32>,
) -> Result<FuelTransaction> {
Ok(self.uncached_client().estimate_predicates(tx).await?)
}
async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
Provider::consensus_parameters(self).await
}
}
The example uses default values for the asset ID and the exclusion lists. This resolves to the base asset ID and empty vectors for the ID lists respectively:
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Get balances from an address
Get all the spendable balances of all assets for an address. This is different from getting the coins because we only return the numbers (the sum of UTXOs coins amount for each asset ID) and not the UTXOs coins themselves.
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Retrying requests
The Provider can be configured to retry a request upon receiving a io::Error.
Note: Currently all node errors are received as
io::Errors. So, if configured, a retry will happen even if, for example, a transaction failed to verify.
We can configure the number of retry attempts and the retry strategy as detailed below.
RetryConfig
The retry behavior can be altered by giving a custom RetryConfig. It allows for configuring the maximum number of attempts and the interval strategy used.
use std::{fmt::Debug, future::Future, num::NonZeroU32, time::Duration};
use fuels_core::types::errors::{error, Result};
/// A set of strategies to control retry intervals between attempts.
///
/// The `Backoff` enum defines different strategies for managing intervals between retry attempts.
/// Each strategy allows you to customize the waiting time before a new attempt based on the
/// number of attempts made.
///
/// # Variants
///
/// - `Linear(Duration)`: Increases the waiting time linearly with each attempt.
/// - `Exponential(Duration)`: Doubles the waiting time with each attempt.
/// - `Fixed(Duration)`: Uses a constant waiting time between attempts.
///
/// # Examples
///
/// ```rust
/// use std::time::Duration;
/// use fuels_accounts::provider::Backoff;
///
/// let linear_backoff = Backoff::Linear(Duration::from_secs(2));
/// let exponential_backoff = Backoff::Exponential(Duration::from_secs(1));
/// let fixed_backoff = Backoff::Fixed(Duration::from_secs(5));
/// ```
//ANCHOR: backoff
#[derive(Debug, Clone)]
pub enum Backoff {
Linear(Duration),
Exponential(Duration),
Fixed(Duration),
}
//ANCHOR_END: backoff
impl Default for Backoff {
fn default() -> Self {
Backoff::Linear(Duration::from_millis(10))
}
}
impl Backoff {
pub fn wait_duration(&self, attempt: u32) -> Duration {
match self {
Backoff::Linear(base_duration) => *base_duration * (attempt + 1),
Backoff::Exponential(base_duration) => *base_duration * 2u32.pow(attempt),
Backoff::Fixed(interval) => *interval,
}
}
}
/// Configuration for controlling retry behavior.
///
/// The `RetryConfig` struct encapsulates the configuration parameters for controlling the retry behavior
/// of asynchronous actions. It includes the maximum number of attempts and the interval strategy from
/// the `Backoff` enum that determines how much time to wait between retry attempts.
///
/// # Fields
///
/// - `max_attempts`: The maximum number of attempts before giving up.
/// - `interval`: The chosen interval strategy from the `Backoff` enum.
///
/// # Examples
///
/// ```rust
/// use std::num::NonZeroUsize;
/// use std::time::Duration;
/// use fuels_accounts::provider::{Backoff, RetryConfig};
///
/// let max_attempts = 5;
/// let interval_strategy = Backoff::Exponential(Duration::from_secs(1));
///
/// let retry_config = RetryConfig::new(max_attempts, interval_strategy).unwrap();
/// ```
// ANCHOR: retry_config
#[derive(Clone, Debug)]
pub struct RetryConfig {
max_attempts: NonZeroU32,
interval: Backoff,
}
// ANCHOR_END: retry_config
impl RetryConfig {
pub fn new(max_attempts: u32, interval: Backoff) -> Result<Self> {
let max_attempts = NonZeroU32::new(max_attempts)
.ok_or_else(|| error!(Other, "`max_attempts` must be greater than `0`"))?;
Ok(RetryConfig {
max_attempts,
interval,
})
}
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: NonZeroU32::new(1).expect("should not fail"),
interval: Default::default(),
}
}
}
/// Retries an asynchronous action with customizable retry behavior.
///
/// This function takes an asynchronous action represented by a closure `action`.
/// The action is executed repeatedly with backoff and retry logic based on the
/// provided `retry_config` and the `should_retry` condition.
///
/// The `action` closure should return a `Future` that resolves to a `Result<T, K>`,
/// where `T` represents the success type and `K` represents the error type.
///
/// # Parameters
///
/// - `action`: The asynchronous action to be retried.
/// - `retry_config`: A reference to the retry configuration.
/// - `should_retry`: A closure that determines whether to retry based on the result.
///
/// # Return
///
/// Returns `Ok(T)` if the action succeeds without requiring further retries.
/// Returns `Err(Error)` if the maximum number of attempts is reached and the action
/// still fails. If a retryable error occurs during the attempts, the error will
/// be returned if the `should_retry` condition allows further retries.
pub(crate) async fn retry<Fut, T, ShouldRetry>(
mut action: impl FnMut() -> Fut,
retry_config: &RetryConfig,
should_retry: ShouldRetry,
) -> T
where
Fut: Future<Output = T>,
ShouldRetry: Fn(&T) -> bool,
{
let mut last_result = None;
for attempt in 0..retry_config.max_attempts.into() {
let result = action().await;
if should_retry(&result) {
last_result = Some(result)
} else {
return result;
}
tokio::time::sleep(retry_config.interval.wait_duration(attempt)).await;
}
last_result.expect("should not happen")
}
#[cfg(test)]
mod tests {
mod retry_until {
use std::time::{Duration, Instant};
use fuels_core::types::errors::{error, Result};
use tokio::sync::Mutex;
use crate::provider::{retry_util, Backoff, RetryConfig};
#[tokio::test]
async fn returns_last_received_response() -> Result<()> {
// given
let err_msgs = ["err1", "err2", "err3"];
let number_of_attempts = Mutex::new(0usize);
let will_always_fail = || async {
let msg = err_msgs[*number_of_attempts.lock().await];
*number_of_attempts.lock().await += 1;
msg
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?;
// when
let response =
retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await;
// then
assert_eq!(response, "err3");
Ok(())
}
#[tokio::test]
async fn stops_retrying_when_predicate_is_satisfied() -> Result<()> {
// given
let values = Mutex::new(vec![1, 2, 3]);
let will_always_fail = || async { values.lock().await.pop().unwrap() };
let should_retry_fn = |res: &i32| *res != 2;
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?;
// when
let response =
retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await;
// then
assert_eq!(response, 2);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_fixed() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Fixed(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.all(|(current_timestamp, the_next_timestamp)| {
the_next_timestamp.duration_since(*current_timestamp)
>= Duration::from_millis(100)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_linear() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.enumerate()
.all(|(attempt, (current_timestamp, the_next_timestamp))| {
the_next_timestamp.duration_since(*current_timestamp)
>= (Duration::from_millis(100) * (attempt + 1) as u32)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_exponential() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options =
RetryConfig::new(3, Backoff::Exponential(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.enumerate()
.all(|(attempt, (current_timestamp, the_next_timestamp))| {
the_next_timestamp.duration_since(*current_timestamp)
>= (Duration::from_millis(100) * (2_usize.pow((attempt) as u32)) as u32)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Interval strategy - Backoff
Backoff defines different strategies for managing intervals between retry attempts.
Each strategy allows you to customize the waiting time before a new attempt based on the number of attempts made.
Variants
Linear(Duration):DefaultIncreases the waiting time linearly with each attempt.Exponential(Duration): Doubles the waiting time with each attempt.Fixed(Duration): Uses a constant waiting time between attempts.
use std::{fmt::Debug, future::Future, num::NonZeroU32, time::Duration};
use fuels_core::types::errors::{error, Result};
/// A set of strategies to control retry intervals between attempts.
///
/// The `Backoff` enum defines different strategies for managing intervals between retry attempts.
/// Each strategy allows you to customize the waiting time before a new attempt based on the
/// number of attempts made.
///
/// # Variants
///
/// - `Linear(Duration)`: Increases the waiting time linearly with each attempt.
/// - `Exponential(Duration)`: Doubles the waiting time with each attempt.
/// - `Fixed(Duration)`: Uses a constant waiting time between attempts.
///
/// # Examples
///
/// ```rust
/// use std::time::Duration;
/// use fuels_accounts::provider::Backoff;
///
/// let linear_backoff = Backoff::Linear(Duration::from_secs(2));
/// let exponential_backoff = Backoff::Exponential(Duration::from_secs(1));
/// let fixed_backoff = Backoff::Fixed(Duration::from_secs(5));
/// ```
//ANCHOR: backoff
#[derive(Debug, Clone)]
pub enum Backoff {
Linear(Duration),
Exponential(Duration),
Fixed(Duration),
}
//ANCHOR_END: backoff
impl Default for Backoff {
fn default() -> Self {
Backoff::Linear(Duration::from_millis(10))
}
}
impl Backoff {
pub fn wait_duration(&self, attempt: u32) -> Duration {
match self {
Backoff::Linear(base_duration) => *base_duration * (attempt + 1),
Backoff::Exponential(base_duration) => *base_duration * 2u32.pow(attempt),
Backoff::Fixed(interval) => *interval,
}
}
}
/// Configuration for controlling retry behavior.
///
/// The `RetryConfig` struct encapsulates the configuration parameters for controlling the retry behavior
/// of asynchronous actions. It includes the maximum number of attempts and the interval strategy from
/// the `Backoff` enum that determines how much time to wait between retry attempts.
///
/// # Fields
///
/// - `max_attempts`: The maximum number of attempts before giving up.
/// - `interval`: The chosen interval strategy from the `Backoff` enum.
///
/// # Examples
///
/// ```rust
/// use std::num::NonZeroUsize;
/// use std::time::Duration;
/// use fuels_accounts::provider::{Backoff, RetryConfig};
///
/// let max_attempts = 5;
/// let interval_strategy = Backoff::Exponential(Duration::from_secs(1));
///
/// let retry_config = RetryConfig::new(max_attempts, interval_strategy).unwrap();
/// ```
// ANCHOR: retry_config
#[derive(Clone, Debug)]
pub struct RetryConfig {
max_attempts: NonZeroU32,
interval: Backoff,
}
// ANCHOR_END: retry_config
impl RetryConfig {
pub fn new(max_attempts: u32, interval: Backoff) -> Result<Self> {
let max_attempts = NonZeroU32::new(max_attempts)
.ok_or_else(|| error!(Other, "`max_attempts` must be greater than `0`"))?;
Ok(RetryConfig {
max_attempts,
interval,
})
}
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_attempts: NonZeroU32::new(1).expect("should not fail"),
interval: Default::default(),
}
}
}
/// Retries an asynchronous action with customizable retry behavior.
///
/// This function takes an asynchronous action represented by a closure `action`.
/// The action is executed repeatedly with backoff and retry logic based on the
/// provided `retry_config` and the `should_retry` condition.
///
/// The `action` closure should return a `Future` that resolves to a `Result<T, K>`,
/// where `T` represents the success type and `K` represents the error type.
///
/// # Parameters
///
/// - `action`: The asynchronous action to be retried.
/// - `retry_config`: A reference to the retry configuration.
/// - `should_retry`: A closure that determines whether to retry based on the result.
///
/// # Return
///
/// Returns `Ok(T)` if the action succeeds without requiring further retries.
/// Returns `Err(Error)` if the maximum number of attempts is reached and the action
/// still fails. If a retryable error occurs during the attempts, the error will
/// be returned if the `should_retry` condition allows further retries.
pub(crate) async fn retry<Fut, T, ShouldRetry>(
mut action: impl FnMut() -> Fut,
retry_config: &RetryConfig,
should_retry: ShouldRetry,
) -> T
where
Fut: Future<Output = T>,
ShouldRetry: Fn(&T) -> bool,
{
let mut last_result = None;
for attempt in 0..retry_config.max_attempts.into() {
let result = action().await;
if should_retry(&result) {
last_result = Some(result)
} else {
return result;
}
tokio::time::sleep(retry_config.interval.wait_duration(attempt)).await;
}
last_result.expect("should not happen")
}
#[cfg(test)]
mod tests {
mod retry_until {
use std::time::{Duration, Instant};
use fuels_core::types::errors::{error, Result};
use tokio::sync::Mutex;
use crate::provider::{retry_util, Backoff, RetryConfig};
#[tokio::test]
async fn returns_last_received_response() -> Result<()> {
// given
let err_msgs = ["err1", "err2", "err3"];
let number_of_attempts = Mutex::new(0usize);
let will_always_fail = || async {
let msg = err_msgs[*number_of_attempts.lock().await];
*number_of_attempts.lock().await += 1;
msg
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?;
// when
let response =
retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await;
// then
assert_eq!(response, "err3");
Ok(())
}
#[tokio::test]
async fn stops_retrying_when_predicate_is_satisfied() -> Result<()> {
// given
let values = Mutex::new(vec![1, 2, 3]);
let will_always_fail = || async { values.lock().await.pop().unwrap() };
let should_retry_fn = |res: &i32| *res != 2;
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(10)))?;
// when
let response =
retry_util::retry(will_always_fail, &retry_options, should_retry_fn).await;
// then
assert_eq!(response, 2);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_fixed() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Fixed(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.all(|(current_timestamp, the_next_timestamp)| {
the_next_timestamp.duration_since(*current_timestamp)
>= Duration::from_millis(100)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_linear() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options = RetryConfig::new(3, Backoff::Linear(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.enumerate()
.all(|(attempt, (current_timestamp, the_next_timestamp))| {
the_next_timestamp.duration_since(*current_timestamp)
>= (Duration::from_millis(100) * (attempt + 1) as u32)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
#[tokio::test]
async fn retry_respects_delay_between_attempts_exponential() -> Result<()> {
// given
let timestamps: Mutex<Vec<Instant>> = Mutex::new(vec![]);
let will_fail_and_record_timestamp = || async {
timestamps.lock().await.push(Instant::now());
Result::<()>::Err(error!(Other, "error"))
};
let should_retry_fn = |_res: &_| -> bool { true };
let retry_options =
RetryConfig::new(3, Backoff::Exponential(Duration::from_millis(100)))?;
// when
let _ = retry_util::retry(
will_fail_and_record_timestamp,
&retry_options,
should_retry_fn,
)
.await;
// then
let timestamps_vec = timestamps.lock().await.clone();
let timestamps_spaced_out_at_least_100_mills = timestamps_vec
.iter()
.zip(timestamps_vec.iter().skip(1))
.enumerate()
.all(|(attempt, (current_timestamp, the_next_timestamp))| {
the_next_timestamp.duration_since(*current_timestamp)
>= (Duration::from_millis(100) * (2_usize.pow((attempt) as u32)) as u32)
});
assert!(
timestamps_spaced_out_at_least_100_mills,
"retry did not wait for the specified time between attempts"
);
Ok(())
}
}
}
Accounts
The ViewOnlyAccount trait provides a common interface to query balances.
The Account trait, in addition to the above, also provides a common interface to retrieve spendable resources or transfer assets. When performing actions in the SDK that lead to a transaction, you will typically need to provide an account that will be used to allocate resources required by the transaction, including transaction fees.
The traits are implemented by the following types:
Transferring assets
An account implements the following methods for transferring assets:
transferforce_transfer_to_contractwithdraw_to_base_layer
The following examples are provided for a Wallet account. A Predicate account would work similarly, but you might need to set its predicate data before attempting to spend resources owned by it.
With wallet.transfer you can initiate a transaction to transfer an asset from your account to a target address.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
You can transfer assets to a contract via wallet.force_transfer_to_contract.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
For transferring assets to the base layer chain, you can use wallet.withdraw_to_base_layer.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
The above example creates an Address from a string and converts it to a Bech32Address. Next, it calls wallet.withdraw_to_base_layer by providing the address, the amount to be transferred, and the transaction policies. Lastly, to verify that the transfer succeeded, the relevant message proof is retrieved with provider.get_message_proof, and the amount and the recipient are verified.
Account impersonation
To facilitate account impersonation, the Rust SDK provides the ImpersonatedAccount struct. Since it implements Account, we can use it to simulate ownership of assets held by an account with a given address. This also implies that we can impersonate contract calls from that address. ImpersonatedAccount will only succeed in unlocking assets if the network is set up with utxo_validation = false.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Managing wallets
You can use wallets for many important things, for instance:
- Checking your balance
- Transferring coins to a destination address or contract
- Signing messages and transactions
- Paying for network fees when sending transactions or deploying smart contracts
The SDK gives you many different ways to create and access wallets. Let's explore these different approaches in the following sub-chapters.
Note: Keep in mind that you should never share your private/secret key. And in the case of wallets that were derived from a mnemonic phrase, never share your mnemonic phrase. If you're planning on storing the wallet on disk, do not store the plain private/secret key and do not store the plain mnemonic phrase. Instead, use
Wallet::encryptto encrypt its content first before saving it to disk.
Creating a wallet from a private key
A new wallet with a randomly generated private key can be created by supplying Option<Provider>.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Alternatively, you can create a wallet from a predefined SecretKey.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Note: if
Noneis supplied instead of a provider, any transaction related to the wallet will result in an error until a provider is linked withset_provider(). The optional parameter enables defining owners (wallet addresses) of genesis coins before a provider is launched.
Creating a wallet from mnemonic phrases
A mnemonic phrase is a cryptographically-generated sequence of words that's used to derive a private key. For instance: "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; would generate the address 0xdf9d0e6c6c5f5da6e82e5e1a77974af6642bdb450a10c43f0c6910a212600185.
In addition to that, we also support Hierarchical Deterministic Wallets and derivation paths. You may recognize the string "m/44'/60'/0'/0/0" from somewhere; that's a derivation path. In simple terms, it's a way to derive many wallets from a single root wallet.
The SDK gives you two wallets from mnemonic instantiation methods: one that takes a derivation path (Wallet::new_from_mnemonic_phrase_with_path) and one that uses the default derivation path, in case you don't want or don't need to configure that (Wallet::new_from_mnemonic_phrase).
Here's how you can create wallets with both mnemonic phrases and derivation paths:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Wallet Access
The kinds of operations we can perform with a Wallet instance depend on
whether or not we have access to the wallet's private key.
In order to differentiate between Wallet instances that know their private key
and those that do not, we use the WalletUnlocked and Wallet types
respectively.
Wallet States
The WalletUnlocked type represents a wallet whose private key is known and
stored internally in memory. A wallet must be of type WalletUnlocked in order
to perform operations that involve signing messages or
transactions.
You can learn more about signing here.
The Wallet type represents a wallet whose private key is not known or stored
in memory. Instead, Wallet only knows its public address. A Wallet cannot be
used to sign transactions, however it may still perform a whole suite of useful
operations including listing transactions, assets, querying balances, and so on.
Note that the WalletUnlocked type provides a Deref implementation targeting
its inner Wallet type. This means that all methods available on the Wallet
type are also available on the WalletUnlocked type. In other words,
WalletUnlocked can be thought of as a thin wrapper around Wallet that
provides greater access via its private key.
Transitioning States
A Wallet instance can be unlocked by providing the private key:
let wallet_unlocked = wallet_locked.unlock(private_key);
A WalletUnlocked instance can be locked using the lock method:
let wallet_locked = wallet_unlocked.lock();
Most wallet constructors that create or generate a new wallet are provided on
the WalletUnlocked type. Consider locking the wallet with the lock method after the new private
key has been handled in order to reduce the scope in which the wallet's private
key is stored in memory.
Design Guidelines
When designing APIs that accept a wallet as an input, we should think carefully
about the kind of access that we require. API developers should aim to minimise
their usage of WalletUnlocked in order to ensure private keys are stored in
memory no longer than necessary to reduce the surface area for attacks and
vulnerabilities in downstream libraries and applications.
Encrypting and storing wallets
Creating a wallet and storing an encrypted JSON wallet on disk
You can also manage a wallet using JSON wallets that are securely encrypted and stored on the disk. This makes it easier to manage multiple wallets, especially for testing purposes.
You can create a random wallet and, at the same time, encrypt and store it. Then, later, you can recover the wallet if you know the master password:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Encrypting and storing a wallet created from a mnemonic or private key
If you have already created a wallet using a mnemonic phrase or a private key, you can also encrypt it and save it to disk:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Checking balances and coins
In the Fuel network, each UTXO corresponds to a unique coin, and said coin has a corresponding amount (the same way a dollar bill has either 10$ or 5$ face value). So, when you want to query the balance for a given asset ID, you want to query the sum of the amount in each unspent coin. This querying is done very easily with a wallet:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
If you want to query all the balances (i.e., get the balance for each asset ID in that wallet), you can use the get_balances method:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
The return type is a HashMap, where the key is the asset ID's hex string, and the value is the corresponding balance. For example, we can get the base asset balance with:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Setting up test wallets
You'll often want to create one or more test wallets when testing your contracts. Here's how to do it.
Setting up multiple test wallets
If you need multiple test wallets, they can be set up using the launch_custom_provider_and_get_wallets method.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
You can customize your test wallets via WalletsConfig.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Note Wallets generated with
launch_provider_and_get_walletorlaunch_custom_provider_and_get_walletswill have deterministic addresses.
Setting up a test wallet with multiple random assets
You can create a test wallet containing multiple assets (including the base asset to pay for gas).
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
- coins:
Vec<(UtxoId, Coin)>hasnum_assets*coins_per_assetscoins (UTXOs) - asset_ids:
Vec<AssetId>contains thenum_assetsrandomly generatedAssetIds (always includes the base asset)
Setting up a test wallet with multiple custom assets
You can also create assets with specific AssetIds, coin amounts, and number of coins.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
This can also be achieved directly with the WalletsConfig.
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Note In this case, you need to manually add the base asset and the corresponding number of coins and coin amount
Setting up assets
The Fuel blockchain holds many different assets; you can create your asset with its unique AssetId or create random assets for testing purposes.
You can use only one asset to pay for transaction fees and gas: the base asset, whose AssetId is 0x000...0, a 32-byte zeroed value.
For testing purposes, you can configure coins and amounts for assets. You can use setup_multiple_assets_coins:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Note If setting up multiple assets, one of these assets will always be the base asset.
If you want to create coins only with the base asset, then you can use:
#[cfg(test)]
mod tests {
use std::time::Duration;
use fuels::prelude::Result;
#[ignore = "testnet currently not compatible with the sdk"]
#[tokio::test]
async fn connect_to_fuel_node() -> Result<()> {
// ANCHOR: connect_to_testnet
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Create a provider pointing to the testnet.
let provider = Provider::connect("testnet.fuel.network").await.unwrap();
// Setup a private key
let secret = SecretKey::from_str(
"a1447cd75accc6b71a976fd3401a1f6ce318d27ba660b0315ee6ac347bf39568",
)?;
// Create the wallet
let wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// Get the wallet address. Used later with the faucet
dbg!(wallet.address().to_string());
// ANCHOR_END: connect_to_testnet
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let port = provider.url().split(':').last().unwrap();
// ANCHOR: local_node_address
let _provider = Provider::connect(format!("127.0.0.1:{port}")).await?;
// ANCHOR_END: local_node_address
Ok(())
}
#[tokio::test]
async fn query_the_blockchain() -> Result<()> {
// ANCHOR: setup_test_blockchain
use fuels::prelude::*;
// Set up our test blockchain.
// Create a random wallet (more on wallets later).
// ANCHOR: setup_single_asset
let wallet = WalletUnlocked::new_random(None);
// How many coins in our wallet.
let number_of_coins = 1;
// The amount/value in each coin in our wallet.
let amount_per_coin = 3;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
// ANCHOR_END: setup_single_asset
// ANCHOR: configure_retry
let retry_config = RetryConfig::new(3, Backoff::Fixed(Duration::from_secs(2)))?;
let provider = setup_test_provider(coins.clone(), vec![], None, None)
.await?
.with_retry_config(retry_config);
// ANCHOR_END: configure_retry
// ANCHOR_END: setup_test_blockchain
// ANCHOR: get_coins
let consensus_parameters = provider.consensus_parameters().await?;
let coins = provider
.get_coins(wallet.address(), *consensus_parameters.base_asset_id())
.await?;
assert_eq!(coins.len(), 1);
// ANCHOR_END: get_coins
// ANCHOR: get_spendable_resources
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: 1,
..Default::default()
};
let spendable_resources = provider.get_spendable_resources(filter).await?;
assert_eq!(spendable_resources.len(), 1);
// ANCHOR_END: get_spendable_resources
// ANCHOR: get_balances
let _balances = provider.get_balances(wallet.address()).await?;
// ANCHOR_END: get_balances
Ok(())
}
}
Note Choosing a large number of coins and assets for
setup_multiple_assets_coinsorsetup_single_asset_coinscan lead to considerable runtime for these methods. This will be improved in the future but for now, we recommend using up to 1_000_000 coins, or 1000 coins and assets simultaneously.
Signing
Once you've instantiated your wallet in an unlocked state using one of the previously discussed methods, you can sign a message with wallet.sign. Below is a full example of how to sign and recover a message.
use std::collections::HashMap;
use async_trait::async_trait;
use fuel_core_client::client::pagination::{PaginatedResult, PaginationRequest};
use fuel_tx::{Output, Receipt, TxId, TxPointer, UtxoId};
use fuel_types::{AssetId, Bytes32, ContractId, Nonce};
use fuels_core::types::{
bech32::{Bech32Address, Bech32ContractId},
coin::Coin,
coin_type::CoinType,
coin_type_id::CoinTypeId,
errors::Result,
input::Input,
message::Message,
transaction::{Transaction, TxPolicies},
transaction_builders::{BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder},
transaction_response::TransactionResponse,
};
use crate::{
accounts_utils::{
add_base_change_if_needed, available_base_assets_and_amount, calculate_missing_base_amount,
extract_message_nonce, split_into_utxo_ids_and_nonces,
},
provider::{Provider, ResourceFilter},
};
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone {
fn address(&self) -> &Bech32Address;
fn try_provider(&self) -> Result<&Provider>;
async fn get_transactions(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
Ok(self
.try_provider()?
.get_transactions_by_owner(self.address(), request)
.await?)
}
/// Gets all unspent coins of asset `asset_id` owned by the account.
async fn get_coins(&self, asset_id: AssetId) -> Result<Vec<Coin>> {
Ok(self
.try_provider()?
.get_coins(self.address(), asset_id)
.await?)
}
/// Get the balance of all spendable coins `asset_id` for address `address`. This is different
/// from getting coins because we are just returning a number (the sum of UTXOs amount) instead
/// of the UTXOs.
async fn get_asset_balance(&self, asset_id: &AssetId) -> Result<u64> {
self.try_provider()?
.get_asset_balance(self.address(), *asset_id)
.await
}
/// Gets all unspent messages owned by the account.
async fn get_messages(&self) -> Result<Vec<Message>> {
Ok(self.try_provider()?.get_messages(self.address()).await?)
}
/// Get all the spendable balances of all assets for the account. This is different from getting
/// the coins because we are only returning the sum of UTXOs coins amount and not the UTXOs
/// coins themselves.
async fn get_balances(&self) -> Result<HashMap<String, u128>> {
self.try_provider()?.get_balances(self.address()).await
}
/// Get some spendable resources (coins and messages) of asset `asset_id` owned by the account
/// that add up at least to amount `amount`. The returned coins (UTXOs) are actual coins that
/// can be spent. The number of UXTOs is optimized to prevent dust accumulation.
async fn get_spendable_resources(
&self,
asset_id: AssetId,
amount: u64,
excluded_coins: Option<Vec<CoinTypeId>>,
) -> Result<Vec<CoinType>> {
let (excluded_utxos, excluded_message_nonces) =
split_into_utxo_ids_and_nonces(excluded_coins);
let filter = ResourceFilter {
from: self.address().clone(),
asset_id: Some(asset_id),
amount,
excluded_utxos,
excluded_message_nonces,
};
self.try_provider()?.get_spendable_resources(filter).await
}
/// Returns a vector containing the output coin and change output given an asset and amount
fn get_asset_outputs_for_amount(
&self,
to: &Bech32Address,
asset_id: AssetId,
amount: u64,
) -> Vec<Output> {
vec![
Output::coin(to.into(), amount, asset_id),
// Note that the change will be computed by the node.
// Here we only have to tell the node who will own the change and its asset ID.
Output::change(self.address().into(), 0, asset_id),
]
}
/// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given
/// asset ID and amount.
async fn get_asset_inputs_for_amount(
&self,
asset_id: AssetId,
amount: u64,
excluded_coins: Option<Vec<CoinTypeId>>,
) -> Result<Vec<Input>>;
/// Add base asset inputs to the transaction to cover the estimated fee
/// and add a change output for the base asset if needed.
/// Requires contract inputs to be at the start of the transactions inputs vec
/// so that their indexes are retained
async fn adjust_for_fee<Tb: TransactionBuilder + Sync>(
&self,
tb: &mut Tb,
used_base_amount: u64,
) -> Result<()> {
let provider = self.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let (base_assets, base_amount) =
available_base_assets_and_amount(tb, consensus_parameters.base_asset_id());
let missing_base_amount =
calculate_missing_base_amount(tb, base_amount, used_base_amount, provider).await?;
if missing_base_amount > 0 {
let new_base_inputs = self
.get_asset_inputs_for_amount(
*consensus_parameters.base_asset_id(),
missing_base_amount,
Some(base_assets),
)
.await
// if there query fails do nothing
.unwrap_or_default();
tb.inputs_mut().extend(new_base_inputs);
};
add_base_change_if_needed(tb, self.address(), consensus_parameters.base_asset_id());
Ok(())
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Account: ViewOnlyAccount {
// Add signatures to the builder if the underlying account is a wallet
fn add_witnesses<Tb: TransactionBuilder>(&self, _tb: &mut Tb) -> Result<()> {
Ok(())
}
/// Transfer funds from this account to another `Address`.
/// Fails if amount for asset ID is larger than address's spendable coins.
/// Returns the transaction ID that was sent and the list of receipts.
async fn transfer(
&self,
to: &Bech32Address,
amount: u64,
asset_id: AssetId,
tx_policies: TxPolicies,
) -> Result<(TxId, Vec<Receipt>)> {
let provider = self.try_provider()?;
let inputs = self
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = self.get_asset_outputs_for_amount(to, asset_id, amount);
let mut tx_builder =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_policies);
self.add_witnesses(&mut tx_builder)?;
let consensus_parameters = provider.consensus_parameters().await?;
let used_base_amount = if asset_id == *consensus_parameters.base_asset_id() {
amount
} else {
0
};
self.adjust_for_fee(&mut tx_builder, used_base_amount)
.await?;
let tx = tx_builder.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
Ok((tx_id, receipts))
}
/// Unconditionally transfers `balance` of type `asset_id` to
/// the contract at `to`.
/// Fails if balance for `asset_id` is larger than this account's spendable balance.
/// Returns the corresponding transaction ID and the list of receipts.
///
/// CAUTION !!!
///
/// This will transfer coins to a contract, possibly leading
/// to the PERMANENT LOSS OF COINS if not used with care.
async fn force_transfer_to_contract(
&self,
to: &Bech32ContractId,
balance: u64,
asset_id: AssetId,
tx_policies: TxPolicies,
) -> Result<(String, Vec<Receipt>)> {
let provider = self.try_provider()?;
let zeroes = Bytes32::zeroed();
let plain_contract_id: ContractId = to.into();
let mut inputs = vec![Input::contract(
UtxoId::new(zeroes, 0),
zeroes,
zeroes,
TxPointer::default(),
plain_contract_id,
)];
inputs.extend(
self.get_asset_inputs_for_amount(asset_id, balance, None)
.await?,
);
let outputs = vec![
Output::contract(0, zeroes, zeroes),
Output::change(self.address().into(), 0, asset_id),
];
// Build transaction and sign it
let mut tb = ScriptTransactionBuilder::prepare_contract_transfer(
plain_contract_id,
balance,
asset_id,
inputs,
outputs,
tx_policies,
);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, balance).await?;
let tx = tb.build(provider).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
Ok((tx_id.to_string(), receipts))
}
/// Withdraws an amount of the base asset to
/// an address on the base chain.
/// Returns the transaction ID, message ID and the list of receipts.
async fn withdraw_to_base_layer(
&self,
to: &Bech32Address,
amount: u64,
tx_policies: TxPolicies,
) -> Result<(TxId, Nonce, Vec<Receipt>)> {
let provider = self.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = self
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount, None)
.await?;
let mut tb = ScriptTransactionBuilder::prepare_message_to_output(
to.into(),
amount,
inputs,
tx_policies,
*consensus_parameters.base_asset_id(),
);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, amount).await?;
let tx = tb.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
let nonce = extract_message_nonce(&receipts)
.expect("MessageId could not be retrieved from tx receipts.");
Ok((tx_id, nonce, receipts))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuel_crypto::{Message, SecretKey, Signature};
use fuel_tx::{Address, ConsensusParameters, Output, Transaction as FuelTransaction};
use fuels_core::{
traits::Signer,
types::{transaction::Transaction, DryRun, DryRunner},
};
use rand::{rngs::StdRng, RngCore, SeedableRng};
use super::*;
use crate::wallet::WalletUnlocked;
#[tokio::test]
async fn sign_and_verify() -> Result<()> {
// ANCHOR: sign_message
let mut rng = StdRng::seed_from_u64(2322u64);
let mut secret_seed = [0u8; 32];
rng.fill_bytes(&mut secret_seed);
let secret = secret_seed.as_slice().try_into()?;
// Create a wallet using the private key created above.
let wallet = WalletUnlocked::new_from_private_key(secret, None);
let message = Message::new("my message".as_bytes());
let signature = wallet.sign(message).await?;
// Check if signature is what we expect it to be
assert_eq!(signature, Signature::from_str("0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d")?);
// Recover address that signed the message
let recovered_address = signature.recover(&message)?;
assert_eq!(wallet.address().hash(), recovered_address.hash());
// Verify signature
signature.verify(&recovered_address, &message)?;
// ANCHOR_END: sign_message
Ok(())
}
#[derive(Default)]
struct MockDryRunner {
c_param: ConsensusParameters,
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl DryRunner for MockDryRunner {
async fn dry_run(&self, _: FuelTransaction) -> Result<DryRun> {
Ok(DryRun {
succeeded: true,
script_gas: 0,
variable_outputs: 0,
})
}
async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
Ok(self.c_param.clone())
}
async fn estimate_gas_price(&self, _block_header: u32) -> Result<u64> {
Ok(0)
}
async fn estimate_predicates(
&self,
_: &FuelTransaction,
_: Option<u32>,
) -> Result<FuelTransaction> {
unimplemented!()
}
}
#[tokio::test]
async fn sign_tx_and_verify() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: sign_tb
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
let wallet = WalletUnlocked::new_from_private_key(secret, None);
// Set up a transaction
let mut tb = {
let input_coin = Input::ResourceSigned {
resource: CoinType::Coin(Coin {
amount: 10000000,
owner: wallet.address().clone(),
..Default::default()
}),
};
let output_coin = Output::coin(
Address::from_str(
"0xc7862855b418ba8f58878db434b21053a61a2025209889cc115989e8040ff077",
)?,
1,
Default::default(),
);
let change = Output::change(wallet.address().into(), 0, Default::default());
ScriptTransactionBuilder::prepare_transfer(
vec![input_coin],
vec![output_coin, change],
Default::default(),
)
};
// Add `Signer` to the transaction builder
tb.add_signer(wallet.clone())?;
// ANCHOR_END: sign_tb
let tx = tb.build(MockDryRunner::default()).await?; // Resolve signatures and add corresponding witness indexes
// Extract the signature from the tx witnesses
let bytes = <[u8; Signature::LEN]>::try_from(tx.witnesses().first().unwrap().as_ref())?;
let tx_signature = Signature::from_bytes(bytes);
// Sign the transaction manually
let message = Message::from_bytes(*tx.id(0.into()));
let signature = wallet.sign(message).await?;
// Check if the signatures are the same
assert_eq!(signature, tx_signature);
// Check if the signature is what we expect it to be
assert_eq!(signature, Signature::from_str("faa616776a1c336ef6257f7cb0cb5cd932180e2d15faba5f17481dae1cbcaf314d94617bd900216a6680bccb1ea62438e4ca93b0d5733d33788ef9d79cc24e9f")?);
// Recover the address that signed the transaction
let recovered_address = signature.recover(&message)?;
assert_eq!(wallet.address().hash(), recovered_address.hash());
// Verify signature
signature.verify(&recovered_address, &message)?;
Ok(())
}
}
Adding Signers to a transaction builder
Every signed resource in the inputs needs to have a witness index that points to a valid witness. Changing the witness index inside an input will change the transaction ID. This means that we need to set all witness indexes before finally signing the transaction. Previously, the user had to make sure that the witness indexes and the order of the witnesses are correct. To automate this process, the SDK will keep track of the signers in the transaction builder and resolve the final transaction automatically. This is done by storing signers until the final transaction is built.
Below is a full example of how to create a transaction builder and add signers to it.
Note: When you add a
Signerto a transaction builder, the signer is stored inside it and the transaction will not be resolved until you callbuild()!
use std::collections::HashMap;
use async_trait::async_trait;
use fuel_core_client::client::pagination::{PaginatedResult, PaginationRequest};
use fuel_tx::{Output, Receipt, TxId, TxPointer, UtxoId};
use fuel_types::{AssetId, Bytes32, ContractId, Nonce};
use fuels_core::types::{
bech32::{Bech32Address, Bech32ContractId},
coin::Coin,
coin_type::CoinType,
coin_type_id::CoinTypeId,
errors::Result,
input::Input,
message::Message,
transaction::{Transaction, TxPolicies},
transaction_builders::{BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder},
transaction_response::TransactionResponse,
};
use crate::{
accounts_utils::{
add_base_change_if_needed, available_base_assets_and_amount, calculate_missing_base_amount,
extract_message_nonce, split_into_utxo_ids_and_nonces,
},
provider::{Provider, ResourceFilter},
};
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ViewOnlyAccount: std::fmt::Debug + Send + Sync + Clone {
fn address(&self) -> &Bech32Address;
fn try_provider(&self) -> Result<&Provider>;
async fn get_transactions(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
Ok(self
.try_provider()?
.get_transactions_by_owner(self.address(), request)
.await?)
}
/// Gets all unspent coins of asset `asset_id` owned by the account.
async fn get_coins(&self, asset_id: AssetId) -> Result<Vec<Coin>> {
Ok(self
.try_provider()?
.get_coins(self.address(), asset_id)
.await?)
}
/// Get the balance of all spendable coins `asset_id` for address `address`. This is different
/// from getting coins because we are just returning a number (the sum of UTXOs amount) instead
/// of the UTXOs.
async fn get_asset_balance(&self, asset_id: &AssetId) -> Result<u64> {
self.try_provider()?
.get_asset_balance(self.address(), *asset_id)
.await
}
/// Gets all unspent messages owned by the account.
async fn get_messages(&self) -> Result<Vec<Message>> {
Ok(self.try_provider()?.get_messages(self.address()).await?)
}
/// Get all the spendable balances of all assets for the account. This is different from getting
/// the coins because we are only returning the sum of UTXOs coins amount and not the UTXOs
/// coins themselves.
async fn get_balances(&self) -> Result<HashMap<String, u128>> {
self.try_provider()?.get_balances(self.address()).await
}
/// Get some spendable resources (coins and messages) of asset `asset_id` owned by the account
/// that add up at least to amount `amount`. The returned coins (UTXOs) are actual coins that
/// can be spent. The number of UXTOs is optimized to prevent dust accumulation.
async fn get_spendable_resources(
&self,
asset_id: AssetId,
amount: u64,
excluded_coins: Option<Vec<CoinTypeId>>,
) -> Result<Vec<CoinType>> {
let (excluded_utxos, excluded_message_nonces) =
split_into_utxo_ids_and_nonces(excluded_coins);
let filter = ResourceFilter {
from: self.address().clone(),
asset_id: Some(asset_id),
amount,
excluded_utxos,
excluded_message_nonces,
};
self.try_provider()?.get_spendable_resources(filter).await
}
/// Returns a vector containing the output coin and change output given an asset and amount
fn get_asset_outputs_for_amount(
&self,
to: &Bech32Address,
asset_id: AssetId,
amount: u64,
) -> Vec<Output> {
vec![
Output::coin(to.into(), amount, asset_id),
// Note that the change will be computed by the node.
// Here we only have to tell the node who will own the change and its asset ID.
Output::change(self.address().into(), 0, asset_id),
]
}
/// Returns a vector consisting of `Input::Coin`s and `Input::Message`s for the given
/// asset ID and amount.
async fn get_asset_inputs_for_amount(
&self,
asset_id: AssetId,
amount: u64,
excluded_coins: Option<Vec<CoinTypeId>>,
) -> Result<Vec<Input>>;
/// Add base asset inputs to the transaction to cover the estimated fee
/// and add a change output for the base asset if needed.
/// Requires contract inputs to be at the start of the transactions inputs vec
/// so that their indexes are retained
async fn adjust_for_fee<Tb: TransactionBuilder + Sync>(
&self,
tb: &mut Tb,
used_base_amount: u64,
) -> Result<()> {
let provider = self.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let (base_assets, base_amount) =
available_base_assets_and_amount(tb, consensus_parameters.base_asset_id());
let missing_base_amount =
calculate_missing_base_amount(tb, base_amount, used_base_amount, provider).await?;
if missing_base_amount > 0 {
let new_base_inputs = self
.get_asset_inputs_for_amount(
*consensus_parameters.base_asset_id(),
missing_base_amount,
Some(base_assets),
)
.await
// if there query fails do nothing
.unwrap_or_default();
tb.inputs_mut().extend(new_base_inputs);
};
add_base_change_if_needed(tb, self.address(), consensus_parameters.base_asset_id());
Ok(())
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Account: ViewOnlyAccount {
// Add signatures to the builder if the underlying account is a wallet
fn add_witnesses<Tb: TransactionBuilder>(&self, _tb: &mut Tb) -> Result<()> {
Ok(())
}
/// Transfer funds from this account to another `Address`.
/// Fails if amount for asset ID is larger than address's spendable coins.
/// Returns the transaction ID that was sent and the list of receipts.
async fn transfer(
&self,
to: &Bech32Address,
amount: u64,
asset_id: AssetId,
tx_policies: TxPolicies,
) -> Result<(TxId, Vec<Receipt>)> {
let provider = self.try_provider()?;
let inputs = self
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = self.get_asset_outputs_for_amount(to, asset_id, amount);
let mut tx_builder =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, tx_policies);
self.add_witnesses(&mut tx_builder)?;
let consensus_parameters = provider.consensus_parameters().await?;
let used_base_amount = if asset_id == *consensus_parameters.base_asset_id() {
amount
} else {
0
};
self.adjust_for_fee(&mut tx_builder, used_base_amount)
.await?;
let tx = tx_builder.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
Ok((tx_id, receipts))
}
/// Unconditionally transfers `balance` of type `asset_id` to
/// the contract at `to`.
/// Fails if balance for `asset_id` is larger than this account's spendable balance.
/// Returns the corresponding transaction ID and the list of receipts.
///
/// CAUTION !!!
///
/// This will transfer coins to a contract, possibly leading
/// to the PERMANENT LOSS OF COINS if not used with care.
async fn force_transfer_to_contract(
&self,
to: &Bech32ContractId,
balance: u64,
asset_id: AssetId,
tx_policies: TxPolicies,
) -> Result<(String, Vec<Receipt>)> {
let provider = self.try_provider()?;
let zeroes = Bytes32::zeroed();
let plain_contract_id: ContractId = to.into();
let mut inputs = vec![Input::contract(
UtxoId::new(zeroes, 0),
zeroes,
zeroes,
TxPointer::default(),
plain_contract_id,
)];
inputs.extend(
self.get_asset_inputs_for_amount(asset_id, balance, None)
.await?,
);
let outputs = vec![
Output::contract(0, zeroes, zeroes),
Output::change(self.address().into(), 0, asset_id),
];
// Build transaction and sign it
let mut tb = ScriptTransactionBuilder::prepare_contract_transfer(
plain_contract_id,
balance,
asset_id,
inputs,
outputs,
tx_policies,
);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, balance).await?;
let tx = tb.build(provider).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
Ok((tx_id.to_string(), receipts))
}
/// Withdraws an amount of the base asset to
/// an address on the base chain.
/// Returns the transaction ID, message ID and the list of receipts.
async fn withdraw_to_base_layer(
&self,
to: &Bech32Address,
amount: u64,
tx_policies: TxPolicies,
) -> Result<(TxId, Nonce, Vec<Receipt>)> {
let provider = self.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = self
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), amount, None)
.await?;
let mut tb = ScriptTransactionBuilder::prepare_message_to_output(
to.into(),
amount,
inputs,
tx_policies,
*consensus_parameters.base_asset_id(),
);
self.add_witnesses(&mut tb)?;
self.adjust_for_fee(&mut tb, amount).await?;
let tx = tb.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
let receipts = tx_status.take_receipts_checked(None)?;
let nonce = extract_message_nonce(&receipts)
.expect("MessageId could not be retrieved from tx receipts.");
Ok((tx_id, nonce, receipts))
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuel_crypto::{Message, SecretKey, Signature};
use fuel_tx::{Address, ConsensusParameters, Output, Transaction as FuelTransaction};
use fuels_core::{
traits::Signer,
types::{transaction::Transaction, DryRun, DryRunner},
};
use rand::{rngs::StdRng, RngCore, SeedableRng};
use super::*;
use crate::wallet::WalletUnlocked;
#[tokio::test]
async fn sign_and_verify() -> Result<()> {
// ANCHOR: sign_message
let mut rng = StdRng::seed_from_u64(2322u64);
let mut secret_seed = [0u8; 32];
rng.fill_bytes(&mut secret_seed);
let secret = secret_seed.as_slice().try_into()?;
// Create a wallet using the private key created above.
let wallet = WalletUnlocked::new_from_private_key(secret, None);
let message = Message::new("my message".as_bytes());
let signature = wallet.sign(message).await?;
// Check if signature is what we expect it to be
assert_eq!(signature, Signature::from_str("0x8eeb238db1adea4152644f1cd827b552dfa9ab3f4939718bb45ca476d167c6512a656f4d4c7356bfb9561b14448c230c6e7e4bd781df5ee9e5999faa6495163d")?);
// Recover address that signed the message
let recovered_address = signature.recover(&message)?;
assert_eq!(wallet.address().hash(), recovered_address.hash());
// Verify signature
signature.verify(&recovered_address, &message)?;
// ANCHOR_END: sign_message
Ok(())
}
#[derive(Default)]
struct MockDryRunner {
c_param: ConsensusParameters,
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl DryRunner for MockDryRunner {
async fn dry_run(&self, _: FuelTransaction) -> Result<DryRun> {
Ok(DryRun {
succeeded: true,
script_gas: 0,
variable_outputs: 0,
})
}
async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
Ok(self.c_param.clone())
}
async fn estimate_gas_price(&self, _block_header: u32) -> Result<u64> {
Ok(0)
}
async fn estimate_predicates(
&self,
_: &FuelTransaction,
_: Option<u32>,
) -> Result<FuelTransaction> {
unimplemented!()
}
}
#[tokio::test]
async fn sign_tx_and_verify() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: sign_tb
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
let wallet = WalletUnlocked::new_from_private_key(secret, None);
// Set up a transaction
let mut tb = {
let input_coin = Input::ResourceSigned {
resource: CoinType::Coin(Coin {
amount: 10000000,
owner: wallet.address().clone(),
..Default::default()
}),
};
let output_coin = Output::coin(
Address::from_str(
"0xc7862855b418ba8f58878db434b21053a61a2025209889cc115989e8040ff077",
)?,
1,
Default::default(),
);
let change = Output::change(wallet.address().into(), 0, Default::default());
ScriptTransactionBuilder::prepare_transfer(
vec![input_coin],
vec![output_coin, change],
Default::default(),
)
};
// Add `Signer` to the transaction builder
tb.add_signer(wallet.clone())?;
// ANCHOR_END: sign_tb
let tx = tb.build(MockDryRunner::default()).await?; // Resolve signatures and add corresponding witness indexes
// Extract the signature from the tx witnesses
let bytes = <[u8; Signature::LEN]>::try_from(tx.witnesses().first().unwrap().as_ref())?;
let tx_signature = Signature::from_bytes(bytes);
// Sign the transaction manually
let message = Message::from_bytes(*tx.id(0.into()));
let signature = wallet.sign(message).await?;
// Check if the signatures are the same
assert_eq!(signature, tx_signature);
// Check if the signature is what we expect it to be
assert_eq!(signature, Signature::from_str("faa616776a1c336ef6257f7cb0cb5cd932180e2d15faba5f17481dae1cbcaf314d94617bd900216a6680bccb1ea62438e4ca93b0d5733d33788ef9d79cc24e9f")?);
// Recover the address that signed the transaction
let recovered_address = signature.recover(&message)?;
assert_eq!(wallet.address().hash(), recovered_address.hash());
// Verify signature
signature.verify(&recovered_address, &message)?;
Ok(())
}
}
Signing a built transaction
If you have a built transaction and want to add a signature, you can use the sign_with method.
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
Generating bindings with abigen
You might have noticed this snippet in the previous sections:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The SDK lets you transform ABI methods of a smart contract, specified as JSON objects (which you can get from Forc), into Rust structs and methods that are type-checked at compile time. In order to call your contracts, scripts or predicates, you first need to generate the Rust bindings for them.
The following subsections contain more details about the abigen! syntax and the code generated from it.
The JSON ABI file
Whether you want to deploy or connect to a pre-existing smart contract, the JSON ABI file is extremely important: it's what tells the SDK about the ABI methods in your smart contracts.
For the same example Sway code as above:
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}
The JSON ABI file looks like this:
$ cat out/release/my-test-abi.json
[
{
"type": "function",
"inputs": [],
"name": "test_function",
"outputs": [
{
"name": "",
"type": "bool",
"components": null
}
]
}
]
The Fuel Rust SDK will take this file as input and generate equivalent methods (and custom types if applicable) that you can call from your Rust code.
abigen
abigen! is a procedural macro -- it generates code. It accepts inputs in the format of:
ProgramType(name="MyProgramType", abi="my_program-abi.json")...
where:
-
ProgramTypeis one of:Contract,ScriptorPredicate, -
nameis the name that will be given to the generated bindings, -
abiis either a path to the JSON ABI file or its actual contents.
So, an abigen! which generates bindings for two contracts and one script looks like this:
extern crate alloc;
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[test]
fn example_of_abigen_usage() {
// ANCHOR: multiple_abigen_program_types
abigen!(
Contract(
name = "ContractA",
abi = "e2e/sway/bindings/sharing_types/contract_a/out/release/contract_a-abi.json"
),
Contract(
name = "ContractB",
abi = "e2e/sway/bindings/sharing_types/contract_b/out/release/contract_b-abi.json"
),
Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
),
Predicate(
name = "MyPredicateEncoder",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
),
);
// ANCHOR_END: multiple_abigen_program_types
}
#[test]
fn macro_deriving() {
// ANCHOR: deriving_traits
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field_a: u8,
}
#[derive(Parameterize, Tokenizable)]
enum SomeEnum {
A(MyStruct),
B(Vec<u64>),
}
// ANCHOR_END: deriving_traits
}
#[test]
fn macro_deriving_extra() {
{
use fuels::{
core as fuels_core_elsewhere,
macros::{Parameterize, Tokenizable},
types as fuels_types_elsewhere,
};
// ANCHOR: deriving_traits_paths
#[derive(Parameterize, Tokenizable)]
#[FuelsCorePath = "fuels_core_elsewhere"]
#[FuelsTypesPath = "fuels_types_elsewhere"]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_paths
}
{
// ANCHOR: deriving_traits_nostd
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
#[NoStd]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_nostd
}
}
}
How does the generated code look?
A rough overview:
pub mod abigen_bindings {
pub mod contract_a_mod {
struct SomeCustomStruct{/*...*/};
// other custom types used in the contract
struct ContractA {/*...*/};
impl ContractA {/*...*/};
// ...
}
pub mod contract_b_mod {
// ...
}
pub mod my_script_mod {
// ...
}
pub mod my_predicate_mod{
// ...
}
pub mod shared_types{
// ...
}
}
pub use contract_a_mod::{/*..*/};
pub use contract_b_mod::{/*..*/};
pub use my_predicate_mod::{/*..*/};
pub use shared_types::{/*..*/};
Each ProgramType gets its own mod based on the name given in the abigen!. Inside the respective mods, the custom types used by that program are generated, and the bindings through which the actual calls can be made.
One extra mod called shared_types is generated if abigen! detects that the given programs share types. Instead of each mod regenerating the type for itself, the type is lifted out into the shared_types module, generated only once, and then shared between all program bindings that use it. Reexports are added to each mod so that even if a type is deemed shared, you can still access it as though each mod had generated the type for itself (i.e. my_contract_mod::SharedType).
A type is deemed shared if its name and definition match up. This can happen either because you've used the same library (a custom one or a type from the stdlib) or because you've happened to define the exact same type.
Finally, pub use statements are inserted, so you don't have to fully qualify the generated types. To avoid conflict, only types that have unique names will get a pub use statement. If you find rustc can't find your type, it might just be that there is another generated type with the same name. To fix the issue just qualify the path by doing abigen_bindings::whatever_contract_mod::TheType.
Note: It is highly encouraged that you generate all your bindings in one
abigen!call. Doing it in this manner will allow type sharing and avoid name collisions you'd normally get when callingabigen!multiple times inside the same namespace. If you choose to proceed otherwise, keep in mind the generated code overview presented above and appropriately separate theabigen!calls into different modules to resolve the collision.
Using the bindings
Let's look at a contract with two methods: initialize_counter(arg: u64) -> u64 and increment_counter(arg: u64) -> u64, with the following JSON ABI:
{
"programType": "contract",
"specVersion": "1",
"encodingVersion": "1",
"concreteTypes": [
{
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
"type": "u64"
}
],
"functions": [
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "initialize_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
},
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "increment_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"metadataTypes": []
}
By doing this:
#[cfg(test)]
mod tests {
use fuels::prelude::Result;
#[tokio::test]
#[allow(unused_variables)]
async fn transform_json_to_bindings() -> Result<()> {
use fuels::test_helpers::launch_provider_and_get_wallet;
let wallet = launch_provider_and_get_wallet().await?;
{
// ANCHOR: use_abigen
use fuels::prelude::*;
// Replace with your own JSON abi path (relative to the root of your crate)
abigen!(Contract(
name = "MyContractName",
abi = "examples/rust_bindings/src/abi.json"
));
// ANCHOR_END: use_abigen
}
{
// ANCHOR: abigen_with_string
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = r#"
{
"programType": "contract",
"specVersion": "1",
"encodingVersion": "1",
"concreteTypes": [
{
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
"type": "u64"
}
],
"functions": [
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "initialize_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
},
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "increment_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"metadataTypes": []
}
"#
));
// ANCHOR_END: abigen_with_string
}
Ok(())
}
}
or this:
#[cfg(test)]
mod tests {
use fuels::prelude::Result;
#[tokio::test]
#[allow(unused_variables)]
async fn transform_json_to_bindings() -> Result<()> {
use fuels::test_helpers::launch_provider_and_get_wallet;
let wallet = launch_provider_and_get_wallet().await?;
{
// ANCHOR: use_abigen
use fuels::prelude::*;
// Replace with your own JSON abi path (relative to the root of your crate)
abigen!(Contract(
name = "MyContractName",
abi = "examples/rust_bindings/src/abi.json"
));
// ANCHOR_END: use_abigen
}
{
// ANCHOR: abigen_with_string
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = r#"
{
"programType": "contract",
"specVersion": "1",
"encodingVersion": "1",
"concreteTypes": [
{
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
"type": "u64"
}
],
"functions": [
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "initialize_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
},
{
"inputs": [
{
"name": "value",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "increment_counter",
"output": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"metadataTypes": []
}
"#
));
// ANCHOR_END: abigen_with_string
}
Ok(())
}
}
you'll generate this (shortened for brevity's sake):
pub mod abigen_bindings {
pub mod my_contract_mod {
#[derive(Debug, Clone)]
pub struct MyContract<A: ::fuels::accounts::Account> {
contract_id: ::fuels::types::bech32::Bech32ContractId,
account: A,
log_decoder: ::fuels::core::codec::LogDecoder,
encoder_config: ::fuels::core::codec::EncoderConfig,
}
impl<A: ::fuels::accounts::Account> MyContract<A> {
pub fn new(
contract_id: impl ::core::convert::Into<::fuels::types::bech32::Bech32ContractId>,
account: A,
) -> Self {
let contract_id: ::fuels::types::bech32::Bech32ContractId = contract_id.into();
let log_decoder = ::fuels::core::codec::LogDecoder::new(
::fuels::core::codec::log_formatters_lookup(vec![], contract_id.clone().into()),
);
let encoder_config = ::fuels::core::codec::EncoderConfig::default();
Self {
contract_id,
account,
log_decoder,
encoder_config,
}
}
pub fn contract_id(&self) -> &::fuels::types::bech32::Bech32ContractId {
&self.contract_id
}
pub fn account(&self) -> A {
self.account.clone()
}
pub fn with_account<U: ::fuels::accounts::Account>(self, account: U) -> MyContract<U> {
MyContract {
contract_id: self.contract_id,
account,
log_decoder: self.log_decoder,
encoder_config: self.encoder_config,
}
}
pub fn with_encoder_config(
mut self,
encoder_config: ::fuels::core::codec::EncoderConfig,
) -> MyContract<A> {
self.encoder_config = encoder_config;
self
}
pub async fn get_balances(
&self,
) -> ::fuels::types::errors::Result<
::std::collections::HashMap<::fuels::types::AssetId, u64>,
> {
::fuels::accounts::ViewOnlyAccount::try_provider(&self.account)?
.get_contract_balances(&self.contract_id)
.await
.map_err(::std::convert::Into::into)
}
pub fn methods(&self) -> MyContractMethods<A> {
MyContractMethods {
contract_id: self.contract_id.clone(),
account: self.account.clone(),
log_decoder: self.log_decoder.clone(),
encoder_config: self.encoder_config.clone(),
}
}
}
pub struct MyContractMethods<A: ::fuels::accounts::Account> {
contract_id: ::fuels::types::bech32::Bech32ContractId,
account: A,
log_decoder: ::fuels::core::codec::LogDecoder,
encoder_config: ::fuels::core::codec::EncoderConfig,
}
impl<A: ::fuels::accounts::Account> MyContractMethods<A> {
#[doc = " This method will read the counter from storage, increment it"]
#[doc = " and write the incremented value to storage"]
pub fn increment_counter(
&self,
value: ::core::primitive::u64,
) -> ::fuels::programs::calls::CallHandler<
A,
::fuels::programs::calls::ContractCall,
::core::primitive::u64,
> {
::fuels::programs::calls::CallHandler::new_contract_call(
self.contract_id.clone(),
self.account.clone(),
::fuels::core::codec::encode_fn_selector("increment_counter"),
&[::fuels::core::traits::Tokenizable::into_token(value)],
self.log_decoder.clone(),
false,
self.encoder_config.clone(),
)
}
pub fn initialize_counter(
&self,
value: ::core::primitive::u64,
) -> ::fuels::programs::calls::CallHandler<
A,
::fuels::programs::calls::ContractCall,
::core::primitive::u64,
> {
::fuels::programs::calls::CallHandler::new_contract_call(
self.contract_id.clone(),
self.account.clone(),
::fuels::core::codec::encode_fn_selector("initialize_counter"),
&[::fuels::core::traits::Tokenizable::into_token(value)],
self.log_decoder.clone(),
false,
self.encoder_config.clone(),
)
}
}
impl<A: ::fuels::accounts::Account> ::fuels::programs::calls::ContractDependency for MyContract<A> {
fn id(&self) -> ::fuels::types::bech32::Bech32ContractId {
self.contract_id.clone()
}
fn log_decoder(&self) -> ::fuels::core::codec::LogDecoder {
self.log_decoder.clone()
}
}
#[derive(Clone, Debug, Default)]
pub struct MyContractConfigurables {
offsets_with_data: ::std::vec::Vec<(u64, ::std::vec::Vec<u8>)>,
encoder: ::fuels::core::codec::ABIEncoder,
}
impl MyContractConfigurables {
pub fn new(encoder_config: ::fuels::core::codec::EncoderConfig) -> Self {
Self {
encoder: ::fuels::core::codec::ABIEncoder::new(encoder_config),
..::std::default::Default::default()
}
}
}
impl From<MyContractConfigurables> for ::fuels::core::Configurables {
fn from(config: MyContractConfigurables) -> Self {
::fuels::core::Configurables::new(config.offsets_with_data)
}
}
}
}
pub use abigen_bindings::my_contract_mod::MyContract;
pub use abigen_bindings::my_contract_mod::MyContractConfigurables;
pub use abigen_bindings::my_contract_mod::MyContractMethods;
Note: that is all generated code. No need to write any of that. Ever. The generated code might look different from one version to another, this is just an example to give you an idea of what it looks like.
Then, you're able to use it to call the actual methods on the deployed contract:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Deploying contracts
There are two main ways of working with contracts in the SDK: deploying a contract with SDK or using the SDK to interact with existing contracts.
Deploying a contract binary
Once you've written a contract in Sway and compiled it with forc build, you'll have in your hands two important artifacts: the compiled binary file and the JSON ABI file.
Note: Read here for more on how to work with Sway.
Below is how you can deploy your contracts using the SDK. For more details about each component in this process, read The abigen macro, The FuelVM binary file, and The JSON ABI file.
First, the Contract::load_from function is used to load a contract binary with a LoadConfiguration. If you are only interested in a single instance of your contract, use the default configuration: LoadConfiguration::default(). After the contract binary is loaded, you can use the deploy() method to deploy the contract to the blockchain.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Alternatively, you can use LoadConfiguration to configure how the contract is loaded. LoadConfiguration let's you:
- Load the same contract binary with
Saltto get a newcontract_id - Change the contract's storage slots
- Update the contract's configurables
Note: The next section will give more information on how
configurablescan be used.
Additionally, you can set custom TxParameters when deploying the loaded contract.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
After the contract is deployed, you can use the contract's methods like this:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Note: When redeploying an existing
Contract, ensure that you initialize it with a unique salt to prevent deployment failures caused by a contract ID collision. To accomplish this, utilize thewith_saltmethod to clone the existingContractwith a new salt.
Configurable constants
In Sway, you can define configurable constants which can be changed during the contract deployment in the SDK. Here is an example how the constants are defined.
contract;
#[allow(dead_code)]
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
configurable {
BOOL: bool = true,
U8: u8 = 8,
U16: u16 = 16,
U32: u32 = 32,
U64: u64 = 63,
U256: u256 = 0x0000000000000000000000000000000000000000000000000000000000000008u256,
B256: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101,
STR_4: str[4] = __to_str_array("fuel"),
TUPLE: (u8, bool) = (8, true),
ARRAY: [u32; 3] = [253, 254, 255],
STRUCT: StructWithGeneric<u8> = StructWithGeneric {
field_1: 8,
field_2: 16,
},
ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}
//U128: u128 = 128, //TODO: add once https://github.com/FuelLabs/sway/issues/5356 is done
abi TestContract {
fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric<u8>, EnumWithGeneric<bool>);
}
impl TestContract for Contract {
fn return_configurables() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric<u8>, EnumWithGeneric<bool>) {
(BOOL, U8, U16, U32, U64, U256, B256, STR_4, TUPLE, ARRAY, STRUCT, ENUM)
}
}
Each of the configurable constants will get a dedicated with method in the SDK. For example, the constant STR_4 will get the with_STR_4 method which accepts the same type as defined in the contract code. Below is an example where we chain several with methods and deploy the contract with the new constants.
use fuels::{
core::codec::EncoderConfig,
prelude::*,
types::{Bits256, SizedAsciiString, U256},
};
#[tokio::test]
async fn contract_default_configurables() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
true,
8,
16,
32,
63,
U256::from(8),
Bits256([1; 32]),
"fuel".try_into()?,
(8, true),
[253, 254, 255],
StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
EnumWithGeneric::VariantOne(true),
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn script_default_configurables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_configurables"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let response = script_instance.main().call().await?;
let expected_value = (
true,
8,
16,
32,
63,
U256::from(8),
Bits256([1; 32]),
"fuel".try_into()?,
(8, true),
[253, 254, 255],
StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
EnumWithGeneric::VariantOne(true),
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn contract_configurables() -> Result<()> {
// ANCHOR: contract_configurables
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyContractConfigurables::default()
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default().with_configurables(configurables),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
// ANCHOR_END: contract_configurables
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn contract_manual_configurables() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/configurables"
)),
Wallets("wallet")
);
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyContractConfigurables::default()
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default(),
)?
.with_configurables(configurables)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn script_configurables() -> Result<()> {
// ANCHOR: script_configurables
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/script_configurables/out/release/script_configurables.bin";
let instance = MyScript::new(wallet, bin_path);
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyScriptConfigurables::new(EncoderConfig {
max_tokens: 5,
..Default::default()
})
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let response = instance
.with_configurables(configurables)
.main()
.call()
.await?;
// ANCHOR_END: script_configurables
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn configurable_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json"
));
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
{
let _configurables = MyScriptConfigurables::default()
.with_STRUCT(new_struct.clone())
.expect("no encoder config, it works");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
// Fails when a wrong encoder config is set
let configurables_error = MyScriptConfigurables::new(encoder_config)
.with_STRUCT(new_struct)
.expect_err("should error");
assert!(configurables_error
.to_string()
.contains("token limit `1` reached while encoding. Try increasing it"),)
}
}
Overriding storage slots
If you use storage in your contract, the default storage values will be generated in a JSON file (e.g. my_contract-storage_slots.json) by the Sway compiler. These are loaded automatically for you when you load a contract binary. If you wish to override some of the defaults, you need to provide the corresponding storage slots manually:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
If you don't have the slot storage file (my_contract-storage_slots.json example from above) for some reason, or you don't wish to load any of the default values, you can disable the auto-loading of storage slots:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Interacting with contracts
If you already have a deployed contract and want to call its methods using the SDK, but without deploying it again, all you need is the contract ID of your deployed contract. You can skip the whole deployment setup and call ::new(contract_id, wallet) directly. For example:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The above example assumes that your contract ID string is encoded in the bech32 format. You can recognize it by the human-readable-part "fuel" followed by the separator "1". However, when using other Fuel tools, you might end up with a hex-encoded contract ID string. In that case, you can create your contract instance as follows:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
You can learn more about the Fuel SDK bech32 types here.
The FuelVM binary file
The command forc build compiles your Sway code and generates the bytecode: the binary code that the Fuel Virtual Machine will interpret. For instance, the smart contract below:
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}
After forc build, will have a binary file that contains:
$ cat out/release/my-test.bin
G4]�]D`I]C�As@
6]C�$@!QK%
This seems very unreadable! But, forc has a nice interpreter for this bytecode: forc parse-bytecode, which will interpret that binary data and output the equivalent FuelVM assembly:
$ forc parse-bytecode out/release/my-test.bin
half-word byte op raw notes
0 0 JI(4) 90 00 00 04 jump to byte 16
1 4 NOOP 47 00 00 00
2 8 Undefined 00 00 00 00 data section offset lo (0)
3 12 Undefined 00 00 00 34 data section offset hi (52)
4 16 LW(63, 12, 1) 5d fc c0 01
5 20 ADD(63, 63, 12) 10 ff f3 00
6 24 LW(17, 6, 73) 5d 44 60 49
7 28 LW(16, 63, 1) 5d 43 f0 01
8 32 EQ(16, 17, 16) 13 41 14 00
9 36 JNZI(16, 11) 73 40 00 0b conditionally jump to byte 44
10 40 RVRT(0) 36 00 00 00
11 44 LW(16, 63, 0) 5d 43 f0 00
12 48 RET(16) 24 40 00 00
13 52 Undefined 00 00 00 00
14 56 Undefined 00 00 00 01
15 60 Undefined 00 00 00 00
16 64 XOR(20, 27, 53) 21 51 bd 4b
If you want to deploy your smart contract using the SDK, this binary file is important; it's what we'll be sending to the FuelVM in a transaction.
Deploying Large Contracts
If your contract exceeds the size limit for a single deployment:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
you can deploy it in segments using a partitioned approach:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
When you convert a standard contract into a loader contract, the following changes occur:
- The original contract code is replaced with the loader contract code.
- The original contract code is split into blobs, which will be deployed via blob transactions before deploying the contract itself.
- The new loader code, when invoked, loads these blobs into memory and executes your original contract.
After deploying the loader contract, you can interact with it just as you would with a standard contract:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
A helper function is available to deploy your contract normally if it is within the size limit, or as a loader contract if it exceeds the limit:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
You also have the option to separate the blob upload from the contract deployment for more granular control:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Alternatively, you can manually split your contract code into blobs and then create and deploy a loader:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Or you can upload the blobs yourself and proceed with just the loader deployment:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Blob Size Considerations
The size of a Blob transaction is constrained by three factors:
- The maximum size of a single transaction:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
- The maximum gas usage for a single transaction:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
- The maximum HTTP body size accepted by the Fuel node.
To estimate an appropriate size for your blobs, you can run:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
However, keep in mind the following limitations:
- The estimation only considers the maximum transaction size, not the max gas usage or HTTP body limit.
- It does not account for any size increase that may occur after the transaction is funded.
Therefore, it is advisable to make your blobs a few percent smaller than the estimated maximum size.
Calling contracts
Once you've deployed your contract, as seen in the previous sections, you'll likely want to:
- Call contract methods;
- Configure call parameters and transaction policies;
- Forward coins and gas in your contract calls;
- Read and interpret returned values and logs.
Here's an example. Suppose your Sway contract has two ABI methods called initialize_counter(u64) and increment_counter(u64). Once you've deployed it the contract, you can call these methods like this:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The example above uses all the default configurations and performs a simple contract call.
Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Next, we'll see how we can further configure the many different parameters in a contract call.
Calls with different wallets
You can use the with_account() method on an existing contract instance as a shorthand for creating a new instance connected to the provided wallet. This lets you make contracts calls with different wallets in a chain like fashion.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Note: connecting a different wallet to an existing instance ignores its set provider in favor of the provider used to deploy the contract. If you have two wallets connected to separate providers (each communicating with a separate fuel-core), the one assigned to the deploying wallet will also be used for contract calls. This behavior is only relevant if multiple providers (i.e. fuel-core instances) are present and can otherwise be ignored.
Transaction policies
Transaction policies are defined as follows:
use std::{collections::HashMap, fmt::Debug};
use async_trait::async_trait;
use fuel_crypto::{Message, Signature};
use fuel_tx::{
field::{
Inputs, Maturity, MintAmount, MintAssetId, Outputs, Policies as PoliciesField,
Script as ScriptField, ScriptData, ScriptGasLimit, WitnessLimit, Witnesses,
},
input::{
coin::{CoinPredicate, CoinSigned},
message::{
MessageCoinPredicate, MessageCoinSigned, MessageDataPredicate, MessageDataSigned,
},
},
policies::PolicyType,
Blob, Bytes32, Cacheable, Chargeable, ConsensusParameters, Create, FormatValidityChecks, Input,
Mint, Output, Salt as FuelSalt, Script, StorageSlot, Transaction as FuelTransaction,
TransactionFee, UniqueIdentifier, Upgrade, Upload, Witness,
};
use fuel_types::{bytes::padded_len_usize, AssetId, ChainId};
use itertools::Itertools;
use crate::{
traits::Signer,
types::{
bech32::Bech32Address,
errors::{error, error_transaction, Error, Result},
DryRunner,
},
utils::{calculate_witnesses_size, sealed},
};
#[derive(Default, Debug, Clone)]
pub struct Transactions {
fuel_transactions: Vec<FuelTransaction>,
}
impl Transactions {
pub fn new() -> Self {
Self::default()
}
pub fn insert(mut self, tx: impl Into<FuelTransaction>) -> Self {
self.fuel_transactions.push(tx.into());
self
}
pub fn as_slice(&self) -> &[FuelTransaction] {
&self.fuel_transactions
}
}
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct MintTransaction {
tx: Box<Mint>,
}
impl From<MintTransaction> for FuelTransaction {
fn from(mint: MintTransaction) -> Self {
(*mint.tx).into()
}
}
impl From<MintTransaction> for Mint {
fn from(tx: MintTransaction) -> Self {
*tx.tx
}
}
impl From<Mint> for MintTransaction {
fn from(tx: Mint) -> Self {
Self { tx: Box::new(tx) }
}
}
impl MintTransaction {
pub fn check_without_signatures(
&self,
block_height: u32,
consensus_parameters: &ConsensusParameters,
) -> Result<()> {
Ok(self
.tx
.check_without_signatures(block_height.into(), consensus_parameters)?)
}
#[must_use]
pub fn id(&self, chain_id: ChainId) -> Bytes32 {
self.tx.id(&chain_id)
}
#[must_use]
pub fn mint_asset_id(&self) -> &AssetId {
self.tx.mint_asset_id()
}
#[must_use]
pub fn mint_amount(&self) -> u64 {
*self.tx.mint_amount()
}
}
#[derive(Default, Debug, Copy, Clone)]
// ANCHOR: tx_policies_struct
pub struct TxPolicies {
tip: Option<u64>,
witness_limit: Option<u64>,
maturity: Option<u64>,
max_fee: Option<u64>,
script_gas_limit: Option<u64>,
}
// ANCHOR_END: tx_policies_struct
impl TxPolicies {
pub fn new(
tip: Option<u64>,
witness_limit: Option<u64>,
maturity: Option<u64>,
max_fee: Option<u64>,
script_gas_limit: Option<u64>,
) -> Self {
Self {
tip,
witness_limit,
maturity,
max_fee,
script_gas_limit,
}
}
pub fn with_tip(mut self, tip: u64) -> Self {
self.tip = Some(tip);
self
}
pub fn tip(&self) -> Option<u64> {
self.tip
}
pub fn with_witness_limit(mut self, witness_limit: u64) -> Self {
self.witness_limit = Some(witness_limit);
self
}
pub fn witness_limit(&self) -> Option<u64> {
self.witness_limit
}
pub fn with_maturity(mut self, maturity: u64) -> Self {
self.maturity = Some(maturity);
self
}
pub fn maturity(&self) -> Option<u64> {
self.maturity
}
pub fn with_max_fee(mut self, max_fee: u64) -> Self {
self.max_fee = Some(max_fee);
self
}
pub fn max_fee(&self) -> Option<u64> {
self.max_fee
}
pub fn with_script_gas_limit(mut self, script_gas_limit: u64) -> Self {
self.script_gas_limit = Some(script_gas_limit);
self
}
pub fn script_gas_limit(&self) -> Option<u64> {
self.script_gas_limit
}
}
use fuel_tx::field::{BytecodeWitnessIndex, Salt, StorageSlots};
use crate::types::coin_type_id::CoinTypeId;
#[derive(Debug, Clone)]
pub enum TransactionType {
Script(ScriptTransaction),
Create(CreateTransaction),
Mint(MintTransaction),
Upload(UploadTransaction),
Upgrade(UpgradeTransaction),
Blob(BlobTransaction),
Unknown,
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait EstimablePredicates: sealed::Sealed {
/// If a transaction contains predicates, we have to estimate them
/// before sending the transaction to the node. The estimation will check
/// all predicates and set the `predicate_gas_used` to the actual consumed gas.
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()>;
}
pub trait GasValidation: sealed::Sealed {
fn validate_gas(&self, _gas_used: u64) -> Result<()>;
}
pub trait ValidatablePredicates: sealed::Sealed {
/// If a transaction contains predicates, we can verify that these predicates validate, ie
/// that they return `true`
fn validate_predicates(
self,
consensus_parameters: &ConsensusParameters,
block_height: u32,
) -> Result<()>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Transaction:
TryFrom<FuelTransaction, Error = Error>
+ Into<FuelTransaction>
+ EstimablePredicates
+ ValidatablePredicates
+ GasValidation
+ Clone
+ Debug
+ sealed::Sealed
{
fn fee_checked_from_tx(
&self,
consensus_parameters: &ConsensusParameters,
gas_price: u64,
) -> Option<TransactionFee>;
fn max_gas(&self, consensus_parameters: &ConsensusParameters) -> u64;
/// Performs all stateless transaction validity checks. This includes the validity
/// of fields according to rules in the specification and validity of signatures.
/// <https://github.com/FuelLabs/fuel-specs/blob/master/src/tx-format/transaction.md>
fn check(&self, block_height: u32, consensus_parameters: &ConsensusParameters) -> Result<()>;
fn id(&self, chain_id: ChainId) -> Bytes32;
fn maturity(&self) -> u32;
fn with_maturity(self, maturity: u32) -> Self;
fn metered_bytes_size(&self) -> usize;
fn inputs(&self) -> &Vec<Input>;
fn outputs(&self) -> &Vec<Output>;
fn witnesses(&self) -> &Vec<Witness>;
fn max_fee(&self) -> Option<u64>;
fn size(&self) -> usize;
fn witness_limit(&self) -> Option<u64>;
fn tip(&self) -> Option<u64>;
fn is_using_predicates(&self) -> bool;
/// Precompute transaction metadata. The metadata is required for
/// `check_without_signatures` validation.
fn precompute(&mut self, chain_id: &ChainId) -> Result<()>;
/// Append witness and return the corresponding witness index
fn append_witness(&mut self, witness: Witness) -> Result<usize>;
fn used_coins(
&self,
base_asset_id: &AssetId,
) -> HashMap<(Bech32Address, AssetId), Vec<CoinTypeId>>;
async fn sign_with(
&mut self,
signer: &(impl Signer + Send + Sync),
chain_id: ChainId,
) -> Result<Signature>;
}
impl TryFrom<TransactionType> for FuelTransaction {
type Error = Error;
fn try_from(value: TransactionType) -> Result<Self> {
match value {
TransactionType::Script(tx) => Ok(tx.into()),
TransactionType::Create(tx) => Ok(tx.into()),
TransactionType::Mint(tx) => Ok(tx.into()),
TransactionType::Upload(tx) => Ok(tx.into()),
TransactionType::Upgrade(tx) => Ok(tx.into()),
TransactionType::Blob(tx) => Ok(tx.into()),
TransactionType::Unknown => Err(error_transaction!(Other, "`Unknown` transaction")),
}
}
}
fn extract_coin_type_id(input: &Input) -> Option<CoinTypeId> {
if let Some(utxo_id) = input.utxo_id() {
return Some(CoinTypeId::UtxoId(*utxo_id));
} else if let Some(nonce) = input.nonce() {
return Some(CoinTypeId::Nonce(*nonce));
}
None
}
pub fn extract_owner_or_recipient(input: &Input) -> Option<Bech32Address> {
let addr = match input {
Input::CoinSigned(CoinSigned { owner, .. })
| Input::CoinPredicate(CoinPredicate { owner, .. }) => Some(owner),
Input::MessageCoinSigned(MessageCoinSigned { recipient, .. })
| Input::MessageCoinPredicate(MessageCoinPredicate { recipient, .. })
| Input::MessageDataSigned(MessageDataSigned { recipient, .. })
| Input::MessageDataPredicate(MessageDataPredicate { recipient, .. }) => Some(recipient),
Input::Contract(_) => None,
};
addr.map(|addr| Bech32Address::from(*addr))
}
macro_rules! impl_tx_wrapper {
($wrapper: ident, $wrapped: ident) => {
#[derive(Debug, Clone)]
pub struct $wrapper {
pub(crate) tx: $wrapped,
pub(crate) is_using_predicates: bool,
}
impl From<$wrapper> for $wrapped {
fn from(tx: $wrapper) -> Self {
tx.tx
}
}
impl From<$wrapper> for FuelTransaction {
fn from(tx: $wrapper) -> Self {
tx.tx.into()
}
}
impl TryFrom<FuelTransaction> for $wrapper {
type Error = Error;
fn try_from(tx: FuelTransaction) -> Result<Self> {
match tx {
FuelTransaction::$wrapped(tx) => Ok(tx.into()),
_ => Err(error_transaction!(
Other,
"couldn't convert Transaction into a wrapper of type $wrapper"
)),
}
}
}
impl From<$wrapped> for $wrapper {
fn from(tx: $wrapped) -> Self {
let is_using_predicates = tx.inputs().iter().any(|input| {
matches!(
input,
Input::CoinPredicate { .. }
| Input::MessageCoinPredicate { .. }
| Input::MessageDataPredicate { .. }
)
});
$wrapper {
tx,
is_using_predicates,
}
}
}
impl ValidatablePredicates for $wrapper {
fn validate_predicates(
self,
_consensus_parameters: &ConsensusParameters,
_block_height: u32,
) -> Result<()> {
// Can no longer validate predicates locally due to the need for blob storage
Ok(())
}
}
impl sealed::Sealed for $wrapper {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl Transaction for $wrapper {
fn max_gas(&self, consensus_parameters: &ConsensusParameters) -> u64 {
self.tx.max_gas(
consensus_parameters.gas_costs(),
consensus_parameters.fee_params(),
)
}
fn fee_checked_from_tx(
&self,
consensus_parameters: &ConsensusParameters,
gas_price: u64,
) -> Option<TransactionFee> {
TransactionFee::checked_from_tx(
&consensus_parameters.gas_costs(),
consensus_parameters.fee_params(),
&self.tx,
gas_price,
)
}
fn check(
&self,
block_height: u32,
consensus_parameters: &ConsensusParameters,
) -> Result<()> {
Ok(self.tx.check(block_height.into(), consensus_parameters)?)
}
fn id(&self, chain_id: ChainId) -> Bytes32 {
self.tx.id(&chain_id)
}
fn maturity(&self) -> u32 {
(*self.tx.maturity()).into()
}
fn with_maturity(mut self, maturity: u32) -> Self {
self.tx.set_maturity(maturity.into());
self
}
fn metered_bytes_size(&self) -> usize {
self.tx.metered_bytes_size()
}
fn inputs(&self) -> &Vec<Input> {
self.tx.inputs()
}
fn outputs(&self) -> &Vec<Output> {
self.tx.outputs()
}
fn witnesses(&self) -> &Vec<Witness> {
self.tx.witnesses()
}
fn is_using_predicates(&self) -> bool {
self.is_using_predicates
}
fn precompute(&mut self, chain_id: &ChainId) -> Result<()> {
Ok(self.tx.precompute(chain_id)?)
}
fn max_fee(&self) -> Option<u64> {
self.tx.policies().get(PolicyType::MaxFee)
}
fn size(&self) -> usize {
use fuel_types::canonical::Serialize;
self.tx.size()
}
fn witness_limit(&self) -> Option<u64> {
self.tx.policies().get(PolicyType::WitnessLimit)
}
fn tip(&self) -> Option<u64> {
self.tx.policies().get(PolicyType::Tip)
}
fn append_witness(&mut self, witness: Witness) -> Result<usize> {
let witness_size = calculate_witnesses_size(
self.tx.witnesses().iter().chain(std::iter::once(&witness)),
);
let new_witnesses_size = padded_len_usize(witness_size)
.ok_or_else(|| error!(Other, "witness size overflow: {witness_size}"))?
as u64;
if new_witnesses_size > self.tx.witness_limit() {
Err(error_transaction!(
Validation,
"Witness limit exceeded. Consider setting the limit manually with \
a transaction builder. The new limit should be: `{new_witnesses_size}`"
))
} else {
let idx = self.tx.witnesses().len();
self.tx.witnesses_mut().push(witness);
Ok(idx)
}
}
fn used_coins(
&self,
base_asset_id: &AssetId,
) -> HashMap<(Bech32Address, AssetId), Vec<CoinTypeId>> {
self.inputs()
.iter()
.filter_map(|input| match input {
Input::Contract { .. } => None,
_ => {
// Not a contract, it's safe to expect.
let owner = extract_owner_or_recipient(input).expect("has owner");
let asset_id = input
.asset_id(base_asset_id)
.expect("has `asset_id`")
.to_owned();
let id = extract_coin_type_id(input).unwrap();
Some(((owner, asset_id), id))
}
})
.into_group_map()
}
async fn sign_with(
&mut self,
signer: &(impl Signer + Send + Sync),
chain_id: ChainId,
) -> Result<Signature> {
let tx_id = self.id(chain_id);
let message = Message::from_bytes(*tx_id);
let signature = signer.sign(message).await?;
self.append_witness(signature.as_ref().into())?;
Ok(signature)
}
}
};
}
impl_tx_wrapper!(ScriptTransaction, Script);
impl_tx_wrapper!(CreateTransaction, Create);
impl_tx_wrapper!(UploadTransaction, Upload);
impl_tx_wrapper!(UpgradeTransaction, Upgrade);
impl_tx_wrapper!(BlobTransaction, Blob);
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl EstimablePredicates for UploadTransaction {
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()> {
let tx = provider
.estimate_predicates(&self.tx.clone().into(), latest_chain_executor_version)
.await?;
tx.as_upload().expect("is upload").clone_into(&mut self.tx);
Ok(())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl EstimablePredicates for UpgradeTransaction {
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()> {
let tx = provider
.estimate_predicates(&self.tx.clone().into(), latest_chain_executor_version)
.await?;
tx.as_upgrade()
.expect("is upgrade")
.clone_into(&mut self.tx);
Ok(())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl EstimablePredicates for CreateTransaction {
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()> {
let tx = provider
.estimate_predicates(&self.tx.clone().into(), latest_chain_executor_version)
.await?;
tx.as_create().expect("is create").clone_into(&mut self.tx);
Ok(())
}
}
impl CreateTransaction {
pub fn salt(&self) -> &FuelSalt {
self.tx.salt()
}
pub fn bytecode_witness_index(&self) -> u16 {
*self.tx.bytecode_witness_index()
}
pub fn storage_slots(&self) -> &Vec<StorageSlot> {
self.tx.storage_slots()
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl EstimablePredicates for ScriptTransaction {
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()> {
let tx = provider
.estimate_predicates(&self.tx.clone().into(), latest_chain_executor_version)
.await?;
tx.as_script().expect("is script").clone_into(&mut self.tx);
Ok(())
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl EstimablePredicates for BlobTransaction {
async fn estimate_predicates(
&mut self,
provider: impl DryRunner,
latest_chain_executor_version: Option<u32>,
) -> Result<()> {
let tx = provider
.estimate_predicates(&self.tx.clone().into(), latest_chain_executor_version)
.await?;
tx.as_blob().expect("is blob").clone_into(&mut self.tx);
Ok(())
}
}
impl GasValidation for CreateTransaction {
fn validate_gas(&self, _gas_used: u64) -> Result<()> {
Ok(())
}
}
impl GasValidation for UploadTransaction {
fn validate_gas(&self, _gas_used: u64) -> Result<()> {
Ok(())
}
}
impl GasValidation for UpgradeTransaction {
fn validate_gas(&self, _gas_used: u64) -> Result<()> {
Ok(())
}
}
impl GasValidation for BlobTransaction {
fn validate_gas(&self, _gas_used: u64) -> Result<()> {
Ok(())
}
}
impl GasValidation for ScriptTransaction {
fn validate_gas(&self, gas_used: u64) -> Result<()> {
if gas_used > *self.tx.script_gas_limit() {
return Err(error_transaction!(
Validation,
"script_gas_limit({}) is lower than the estimated gas_used({})",
self.tx.script_gas_limit(),
gas_used
));
}
Ok(())
}
}
impl ScriptTransaction {
pub fn script(&self) -> &Vec<u8> {
self.tx.script()
}
pub fn script_data(&self) -> &Vec<u8> {
self.tx.script_data()
}
pub fn gas_limit(&self) -> u64 {
*self.tx.script_gas_limit()
}
pub fn with_gas_limit(mut self, gas_limit: u64) -> Self {
*self.tx.script_gas_limit_mut() = gas_limit;
self
}
}
#[cfg(test)]
mod test {
use fuel_tx::policies::Policies;
use super::*;
#[test]
fn append_witnesses_returns_error_when_limit_exceeded() {
let mut tx = ScriptTransaction {
tx: FuelTransaction::script(
0,
vec![],
vec![],
Policies::default(),
vec![],
vec![],
vec![],
),
is_using_predicates: false,
};
let witness = vec![0, 1, 2].into();
let err = tx.append_witness(witness).expect_err("should error");
let expected_err_str = "transaction validation: Witness limit exceeded. \
Consider setting the limit manually with a transaction builder. \
The new limit should be: `16`";
assert_eq!(&err.to_string(), expected_err_str);
}
}
Where:
- Tip - amount to pay the block producer to prioritize the transaction.
- Witness Limit - The maximum amount of witness data allowed for the transaction.
- Maturity - Block until which the transaction cannot be included.
- Max Fee - The maximum fee payable by this transaction.
- Script Gas Limit - The maximum amount of gas the transaction may consume for executing its script code.
When the Script Gas Limit is not set, the Rust SDK will estimate the consumed gas in the background and set it as the limit.
If the Witness Limit is not set, the SDK will set it to the size of all witnesses and signatures defined in the transaction builder.
You can configure these parameters by creating an instance of TxPolicies and passing it to a chain method called with_tx_policies:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
You can also use TxPolicies::default() to use the default values.
This way:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
As you might have noticed, TxPolicies can also be specified when deploying contracts or transferring assets by passing it to the respective methods.
Call parameters
The parameters for a contract call are:
- Amount
- Asset ID
- Gas forwarded
You can use these to forward coins to a contract. You can configure these parameters by creating an instance of CallParameters and passing it to a chain method called call_params.
For instance, suppose the following contract that uses Sway's msg_amount() to return the amount sent in that transaction.
contract;
use std::storage::storage_api::{read, write};
use std::context::msg_amount;
struct MyType {
x: u64,
y: u64,
}
#[allow(dead_code)]
struct Person {
name: str[4],
}
#[allow(dead_code)]
enum State {
A: (),
B: (),
C: (),
}
abi TestContract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64;
#[storage(read, write)]
fn increment_counter(value: u64) -> u64;
#[storage(read)]
fn get_counter() -> u64;
// ANCHOR: low_level_call
#[storage(write)]
fn set_value_multiple_complex(a: MyStruct, b: str[4]);
// ANCHOR_END: low_level_call
#[storage(read)]
fn get_str_value() -> str[4];
#[storage(read)]
fn get_bool_value() -> bool;
#[storage(read)]
fn get_value() -> u64;
fn get(x: u64, y: u64) -> u64;
fn get_alt(x: MyType) -> MyType;
fn get_single(x: u64) -> u64;
fn array_of_structs(p: [Person; 2]) -> [Person; 2];
fn array_of_enums(p: [State; 2]) -> [State; 2];
fn get_array(p: [u64; 2]) -> [u64; 2];
#[payable]
fn get_msg_amount() -> u64;
fn new() -> u64;
}
const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000;
storage {
value: u64 = 0,
value_str: str[4] = __to_str_array("none"),
value_bool: bool = false,
}
pub struct MyStruct {
a: bool,
b: [u64; 3],
}
impl TestContract for Contract {
// ANCHOR: msg_amount
#[payable]
fn get_msg_amount() -> u64 {
msg_amount()
}
// ANCHOR_END: msg_amount
#[storage(write)]
fn initialize_counter(value: u64) -> u64 {
write(COUNTER_KEY, 0, value);
value
}
/// This method will read the counter from storage, increment it
/// and write the incremented value to storage
#[storage(read, write)]
fn increment_counter(value: u64) -> u64 {
let new_value = read::<u64>(COUNTER_KEY, 0).unwrap_or(0) + value;
write(COUNTER_KEY, 0, new_value);
new_value
}
#[storage(read)]
fn get_counter() -> u64 {
read::<u64>(COUNTER_KEY, 0).unwrap_or(0)
}
#[storage(write)]
fn set_value_multiple_complex(a: MyStruct, b: str[4]) {
storage.value.write(a.b[1]);
storage.value_str.write(b);
storage.value_bool.write(a.a);
}
#[storage(read)]
fn get_str_value() -> str[4] {
storage.value_str.read()
}
#[storage(read)]
fn get_bool_value() -> bool {
storage.value_bool.read()
}
#[storage(read)]
fn get_value() -> u64 {
storage.value.read()
}
fn get(x: u64, y: u64) -> u64 {
x + y
}
fn get_alt(t: MyType) -> MyType {
t
}
fn get_single(x: u64) -> u64 {
x
}
fn array_of_structs(p: [Person; 2]) -> [Person; 2] {
p
}
fn array_of_enums(p: [State; 2]) -> [State; 2] {
p
}
fn get_array(p: [u64; 2]) -> [u64; 2] {
p
}
fn new() -> u64 {
12345u64
}
}
Then, in Rust, after setting up and deploying the above contract, you can configure the amount being sent in the transaction like this:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
call_params returns a result to ensure you don't forward assets to a contract method that isn't payable.
In the following example, we try to forward an amount of 100 of the base asset to non_payable. As its name suggests, non_payable isn't annotated with #[payable] in the contract code. Passing CallParameters with an amount other than 0 leads to an error:
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
Note: forwarding gas to a contract call is always possible, regardless of the contract method being non-payable.
You can also use CallParameters::default() to use the default values:
use fuel_tx::Word;
pub const ENUM_DISCRIMINANT_BYTE_WIDTH: usize = 8;
pub const WORD_SIZE: usize = core::mem::size_of::<Word>();
// ANCHOR: default_call_parameters
pub const DEFAULT_CALL_PARAMS_AMOUNT: u64 = 0;
// ANCHOR_END: default_call_parameters
pub const DEFAULT_GAS_ESTIMATION_TOLERANCE: f64 = 0.2;
pub const DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON: u32 = 5;
// The size of a signature inside a transaction `Witness`
pub const WITNESS_STATIC_SIZE: usize = 8;
const SIGNATURE_SIZE: usize = 64;
pub const SIGNATURE_WITNESS_SIZE: usize = WITNESS_STATIC_SIZE + SIGNATURE_SIZE;
This way:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The gas_forwarded parameter defines the limit for the actual contract call as opposed to the gas limit for the whole transaction. This means that it is constrained by the transaction limit. If it is set to an amount greater than the available gas, all available gas will be forwarded.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
If you don't set the call parameters or use CallParameters::default(), the transaction gas limit will be forwarded instead.
Custom asset transfer
The SDK provides the option to transfer assets within the same transaction, when making a contract call. By using add_custom_asset() you specify the asset ID, the amount, and the destination address:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Call response
You've probably noticed that you're often chaining .call().await.unwrap(). That's because:
- You have to choose between
.call()and.simulate()(more on this in the next section). - Contract calls are asynchronous, so you can choose to either
.awaitit or perform concurrent tasks, making full use of Rust's async. .unwrap()theResult<CallResponse, Error>returned by the contract call.
Once you unwrap the CallResponse, you have access to this struct:
use std::fmt::Debug;
use fuel_tx::{Bytes32, Receipt};
use fuels_core::{
codec::{LogDecoder, LogResult},
traits::{Parameterize, Tokenizable},
types::errors::Result,
};
/// [`CallResponse`] is a struct that is returned by a call to the contract or script. Its value
/// field holds the decoded typed value returned by the contract's method. The other field holds all
/// the receipts returned by the call.
#[derive(Debug)]
// ANCHOR: call_response
pub struct CallResponse<D> {
pub value: D,
pub receipts: Vec<Receipt>,
pub gas_used: u64,
pub log_decoder: LogDecoder,
pub tx_id: Option<Bytes32>,
}
// ANCHOR_END: call_response
impl<D> CallResponse<D> {
/// Get the gas used from ScriptResult receipt
fn get_gas_used(receipts: &[Receipt]) -> u64 {
receipts
.iter()
.rfind(|r| matches!(r, Receipt::ScriptResult { .. }))
.expect("could not retrieve ScriptResult")
.gas_used()
.expect("could not retrieve gas used from ScriptResult")
}
pub fn new(
value: D,
receipts: Vec<Receipt>,
log_decoder: LogDecoder,
tx_id: Option<Bytes32>,
) -> Self {
Self {
value,
gas_used: Self::get_gas_used(&receipts),
receipts,
log_decoder,
tx_id,
}
}
pub fn decode_logs(&self) -> LogResult {
self.log_decoder.decode_logs(&self.receipts)
}
pub fn decode_logs_with_type<T: Tokenizable + Parameterize + 'static>(&self) -> Result<Vec<T>> {
self.log_decoder.decode_logs_with_type::<T>(&self.receipts)
}
}
Where value will hold the value returned by its respective contract method, represented by the exact type returned by the FuelVM, E.g., if your contract returns a FuelVM's u64, value's D will be a u64. If it's a FuelVM's tuple (u8,bool), then D will be a (u8,bool). If it's a custom type, for instance, a Sway struct MyStruct containing two components, a u64, and a b256, D will be a struct generated at compile-time, called MyStruct with u64 and a [u8; 32] (the equivalent of b256 in Rust).
receiptswill hold all receipts generated by that specific contract call.gas_usedis the amount of gas consumed by the contract call.tx_idwill hold the ID of the corresponding submitted transaction.
Error handling
You can use the is_ok and is_err methods to check if a contract call Result is Ok or contains an error. These methods will return either true or false.
let is_ok = response.is_ok();
let is_error = response.is_err();
If is_err returns true, you can use the unwrap_err method to unwrap the error message.
if response.is_err() {
let err = response.unwrap_err();
println!("ERROR: {:?}", err);
};
Logs
Whenever you log a value within a contract method, the resulting log entry is added to the log receipt and the variable type is recorded in the contract's ABI. The SDK lets you parse those values into Rust types.
Consider the following contract method:
contract;
use std::{logging::log, string::String};
use contract_logs_abi::ContractLogs;
#[allow(dead_code)]
struct TestStruct {
field_1: bool,
field_2: b256,
field_3: u64,
}
#[allow(dead_code)]
enum TestEnum {
VariantOne: (),
VariantTwo: (),
}
#[allow(dead_code)]
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
#[allow(dead_code)]
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
#[allow(dead_code)]
struct StructWithNestedGeneric<D> {
field_1: D,
field_2: u64,
}
#[allow(dead_code)]
struct StructDeeplyNestedGeneric<D> {
field_1: D,
field_2: u64,
}
impl ContractLogs for Contract {
fn produce_logs_values() {
log(64u64);
log(32u32);
log(16u16);
log(8u8);
}
// ANCHOR: produce_logs
fn produce_logs_variables() {
let f: u64 = 64;
let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a;
let e: str[4] = __to_str_array("Fuel");
let l: [u8; 3] = [1u8, 2u8, 3u8];
log(f);
log(u);
log(e);
log(l);
}
// ANCHOR_END: produce_logs
fn produce_logs_custom_types() {
let f: u64 = 64;
let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a;
let test_struct = TestStruct {
field_1: true,
field_2: u,
field_3: f,
};
let test_enum = TestEnum::VariantTwo;
log(test_struct);
log(test_enum);
log((test_struct, test_enum));
}
fn produce_logs_generic_types() {
let l: [u8; 3] = [1u8, 2u8, 3u8];
let test_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let test_enum = EnumWithGeneric::VariantOne(l);
let test_struct_nested = StructWithNestedGeneric {
field_1: test_struct,
field_2: 64,
};
let test_deeply_nested_generic = StructDeeplyNestedGeneric {
field_1: test_struct_nested,
field_2: 64,
};
log(test_struct);
log(test_enum);
log(test_struct_nested);
log(test_deeply_nested_generic);
}
fn produce_multiple_logs() {
let f: u64 = 64;
let u: b256 = 0xef86afa9696cf0dc6385e2c407a6e159a1103cefb7e2ae0636fb33d3cb2a9e4a;
let e: str[4] = __to_str_array("Fuel");
let l: [u8; 3] = [1u8, 2u8, 3u8];
let test_struct = TestStruct {
field_1: true,
field_2: u,
field_3: f,
};
let test_enum = TestEnum::VariantTwo;
let test_generic_struct = StructWithGeneric {
field_1: test_struct,
field_2: 64,
};
log(64);
log(32u32);
log(16u16);
log(8u8);
log(f);
log(u);
log(e);
log(l);
log(test_struct);
log(test_enum);
log(test_generic_struct);
}
fn produce_bad_logs() {
// produce a custom log with log id 128
// this log id will not be present in abi JSON
asm(r1: 0, r2: 128, r3: 0, r4: 0) {
log r1 r2 r3 r4;
}
log(123);
}
fn produce_string_slice_log() {
log("fuel");
}
fn produce_string_log() {
log(String::from_ascii_str("fuel"));
}
fn produce_bytes_log() {
log(String::from_ascii_str("fuel").as_bytes());
}
fn produce_raw_slice_log() {
log(String::from_ascii_str("fuel").as_raw_slice());
}
fn produce_vec_log() {
let mut v = Vec::new();
v.push(1u16);
v.push(2u16);
v.push(3u16);
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let mut v1 = Vec::new();
v1.push(some_enum);
v1.push(other_enum);
v1.push(some_enum);
let mut v2 = Vec::new();
v2.push(v1);
v2.push(v1);
let mut v3 = Vec::new();
v3.push(v2);
log(v3);
}
}
You can access the logged values in Rust by calling decode_logs_with_type::<T> from a CallResponse, where T is the type of the logged variables you want to retrieve. The result will be a Vec<T>:
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
You can use the decode_logs() function to retrieve a LogResult struct containing a results field that is a vector of Result<String> values representing the success or failure of decoding each log.
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
Due to possible performance hits, it is not recommended to use decode_logs() outside of a debugging scenario.
Note: String slices cannot be logged directly. Use the
__to_str_array()function to convert it to astr[N]first.
Output variables
Sometimes, the contract you call might transfer funds to a specific address, depending on its execution. The underlying transaction for such a contract call has to have the appropriate number of variable outputs to succeed.
Let's say you deployed a contract with the following method:
contract;
use std::{
asset::*,
bytes::Bytes,
constants::ZERO_B256,
context::balance_of,
context::msg_amount,
message::send_message,
};
abi TestFuelCoin {
fn mint_coins(mint_amount: u64);
fn mint_to_addresses(mint_amount: u64, addresses: [Identity; 3]);
fn burn_coins(burn_amount: u64);
fn transfer(coins: u64, asset_id: AssetId, target: Identity);
fn get_balance(target: ContractId, asset_id: AssetId) -> u64;
#[payable]
fn get_msg_amount() -> u64;
fn send_message(recipient: b256, coins: u64);
}
impl TestFuelCoin for Contract {
fn mint_coins(mint_amount: u64) {
mint(ZERO_B256, mint_amount);
}
fn mint_to_addresses(mint_amount: u64, addresses: [Identity; 3]) {
let mut counter = 0;
while counter < 3 {
mint_to(addresses[counter], ZERO_B256, mint_amount);
counter = counter + 1;
}
}
fn burn_coins(burn_amount: u64) {
burn(ZERO_B256, burn_amount);
}
// ANCHOR: variable_outputs
fn transfer(coins: u64, asset_id: AssetId, recipient: Identity) {
transfer(recipient, asset_id, coins);
}
// ANCHOR_END: variable_outputs
fn get_balance(target: ContractId, asset_id: AssetId) -> u64 {
balance_of(target, asset_id)
}
#[payable]
fn get_msg_amount() -> u64 {
msg_amount()
}
fn send_message(recipient: b256, coins: u64) {
let mut data = Bytes::new();
data.push(1u8);
data.push(2u8);
data.push(3u8);
send_message(recipient, data, coins);
}
}
When calling transfer_coins_to_output with the SDK, you can specify the number of variable outputs:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
with_variable_output_policy sets the policy regarding variable outputs. You can either set the number of variable outputs yourself by providing VariableOutputPolicy::Exactly(n) or let the SDK estimate it for you with VariableOutputPolicy::EstimateMinimum. A variable output indicates that the amount and the owner may vary based on transaction execution.
Note: that the Sway
lib-stdfunctionmint_to_addresscallstransfer_to_addressunder the hood, so you need to callwith_variable_output_policyin the Rust SDK tests like you would fortransfer_to_address.
Simulating calls
Sometimes you want to simulate a call to a contract without changing the state of the blockchain. This can be achieved by calling .simulate instead of .call and passing in the desired execution context:
.simulate(Execution::Realistic)simulates the transaction in a manner that closely resembles a real call. You need a wallet with base assets to cover the transaction cost, even though no funds will be consumed. This is useful for validating that a real call would succeed if made at that moment. It allows you to debug issues with your contract without spending gas.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
.simulate(Execution::StateReadOnly)disables many validations, adds fake gas, extra variable outputs, blank witnesses, etc., enabling you to read state even with an account that has no funds.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Calling other contracts
If your contract method is calling other contracts you will have to add the appropriate Inputs and Outputs to your transaction. For your convenience, the CallHandler provides methods that prepare those inputs and outputs for you. You have two methods that you can use: with_contracts(&[&contract_instance, ...]) and with_contract_ids(&[&contract_id, ...]).
with_contracts(&[&contract_instance, ...]) requires contract instances that were created using the abigen macro. When setting the external contracts with this method, logs and require revert errors originating from the external contract can be propagated and decoded by the calling contract.
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
If however, you do not need to decode logs or you do not have a contract instance that was generated using the abigen macro you can use with_contract_ids(&[&contract_id, ...]) and provide the required contract ids.
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
Multiple contract calls
With CallHandler, you can execute multiple contract calls within a single transaction. To achieve this, you first prepare all the contract calls that you want to bundle:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
You can also set call parameters, variable outputs, or external contracts for every contract call, as long as you don't execute it with call() or simulate().
Next, you provide the prepared calls to your CallHandler and optionally configure transaction policies:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Note: any transaction policies configured on separate contract calls are disregarded in favor of the parameters provided to the multi-call
CallHandler.
Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Output values
To get the output values of the bundled calls, you need to provide explicit type annotations when saving the result of call() or simulate() to a variable:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
You can also interact with the CallResponse by moving the type annotation to the invoked method:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Transaction dependency estimation
Previously, we mentioned that a contract call might require you to manually specify external contracts, variable outputs, or output messages. The SDK can also attempt to estimate and set these dependencies for you at the cost of running multiple simulated calls in the background.
The following example uses a contract call that calls an external contract and later mints assets to a specified address. Calling it without including the dependencies will result in a revert:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
As mentioned in previous chapters, you can specify the external contract and add an output variable to resolve this:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
But this requires you to know the contract ID of the external contract and the needed number of output variables. Alternatively, by chaining .estimate_tx_dependencies() instead, the dependencies will be estimated by the SDK and set automatically. The optional parameter is the maximum number of simulation attempts:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The minimal number of attempts corresponds to the number of external contracts and output variables needed and defaults to 10.
Note:
estimate_tx_dependencies()can also be used when working with script calls or multi calls.estimate_tx_dependencies()does not currently resolve the dependencies needed for logging from an external contract. For more information, see here. If no resolution was found after exhausting all simulation attempts, the last received error will be propagated. The same will happen if an error is unrelated to transaction dependencies.
Estimating contract call cost
With the function estimate_transaction_cost(tolerance: Option<f64>, block_horizon: Option<u32>) provided by CallHandler, you can get a cost estimation for a specific call. The return type, TransactionCost, is a struct that contains relevant information for the estimation:
#[cfg(feature = "coin-cache")]
use std::sync::Arc;
use std::{collections::HashMap, fmt::Debug, net::SocketAddr};
mod cache;
mod retry_util;
mod retryable_client;
mod supported_fuel_core_version;
mod supported_versions;
use crate::provider::cache::CacheableRpcs;
pub use cache::TtlConfig;
use cache::{CachedClient, SystemClock};
use chrono::{DateTime, Utc};
use fuel_core_client::client::{
pagination::{PageDirection, PaginatedResult, PaginationRequest},
types::{
balance::Balance,
contract::ContractBalance,
gas_price::{EstimateGasPrice, LatestGasPrice},
},
};
use fuel_core_types::services::executor::TransactionExecutionResult;
use fuel_tx::{
AssetId, ConsensusParameters, Receipt, Transaction as FuelTransaction, TxId, UtxoId,
};
use fuel_types::{Address, BlockHeight, Bytes32, Nonce};
#[cfg(feature = "coin-cache")]
use fuels_core::types::coin_type_id::CoinTypeId;
use fuels_core::{
constants::{DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON, DEFAULT_GAS_ESTIMATION_TOLERANCE},
types::{
bech32::{Bech32Address, Bech32ContractId},
block::{Block, Header},
chain_info::ChainInfo,
coin::Coin,
coin_type::CoinType,
errors::Result,
message::Message,
message_proof::MessageProof,
node_info::NodeInfo,
transaction::{Transaction, Transactions},
transaction_builders::{Blob, BlobId},
transaction_response::TransactionResponse,
tx_status::TxStatus,
DryRun, DryRunner,
},
};
pub use retry_util::{Backoff, RetryConfig};
pub use supported_fuel_core_version::SUPPORTED_FUEL_CORE_VERSION;
use tai64::Tai64;
#[cfg(feature = "coin-cache")]
use tokio::sync::Mutex;
#[cfg(feature = "coin-cache")]
use crate::coin_cache::CoinsCache;
use crate::provider::retryable_client::RetryableClient;
const NUM_RESULTS_PER_REQUEST: i32 = 100;
#[derive(Debug, Clone, PartialEq)]
// ANCHOR: transaction_cost
pub struct TransactionCost {
pub gas_price: u64,
pub gas_used: u64,
pub metered_bytes_size: u64,
pub total_fee: u64,
}
// ANCHOR_END: transaction_cost
pub(crate) struct ResourceQueries {
utxos: Vec<UtxoId>,
messages: Vec<Nonce>,
asset_id: Option<AssetId>,
amount: u64,
}
impl ResourceQueries {
pub fn exclusion_query(&self) -> Option<(Vec<UtxoId>, Vec<Nonce>)> {
if self.utxos.is_empty() && self.messages.is_empty() {
return None;
}
Some((self.utxos.clone(), self.messages.clone()))
}
pub fn spend_query(&self, base_asset_id: AssetId) -> Vec<(AssetId, u64, Option<u32>)> {
vec![(self.asset_id.unwrap_or(base_asset_id), self.amount, None)]
}
}
#[derive(Default)]
// ANCHOR: resource_filter
pub struct ResourceFilter {
pub from: Bech32Address,
pub asset_id: Option<AssetId>,
pub amount: u64,
pub excluded_utxos: Vec<UtxoId>,
pub excluded_message_nonces: Vec<Nonce>,
}
// ANCHOR_END: resource_filter
impl ResourceFilter {
pub fn owner(&self) -> Address {
(&self.from).into()
}
pub(crate) fn resource_queries(&self) -> ResourceQueries {
ResourceQueries {
utxos: self.excluded_utxos.clone(),
messages: self.excluded_message_nonces.clone(),
asset_id: self.asset_id,
amount: self.amount,
}
}
}
/// Encapsulates common client operations in the SDK.
/// Note that you may also use `client`, which is an instance
/// of `FuelClient`, directly, which provides a broader API.
#[derive(Debug, Clone)]
pub struct Provider {
cached_client: CachedClient<RetryableClient>,
#[cfg(feature = "coin-cache")]
coins_cache: Arc<Mutex<CoinsCache>>,
}
impl Provider {
pub async fn from(addr: impl Into<SocketAddr>) -> Result<Self> {
let addr = addr.into();
Self::connect(format!("http://{addr}")).await
}
pub fn set_cache_ttl(&mut self, ttl: TtlConfig) {
self.cached_client.set_ttl(ttl);
}
pub async fn clear_cache(&self) {
self.cached_client.clear().await;
}
pub async fn healthy(&self) -> Result<bool> {
Ok(self.uncached_client().health().await?)
}
/// Connects to an existing node at the given address.
pub async fn connect(url: impl AsRef<str>) -> Result<Provider> {
let client = CachedClient::new(
RetryableClient::connect(&url, Default::default()).await?,
TtlConfig::default(),
SystemClock,
);
Ok(Self {
cached_client: client,
#[cfg(feature = "coin-cache")]
coins_cache: Default::default(),
})
}
pub fn url(&self) -> &str {
self.uncached_client().url()
}
pub async fn blob(&self, blob_id: BlobId) -> Result<Option<Blob>> {
Ok(self
.uncached_client()
.blob(blob_id.into())
.await?
.map(|blob| Blob::new(blob.bytecode)))
}
pub async fn blob_exists(&self, blob_id: BlobId) -> Result<bool> {
Ok(self.uncached_client().blob_exists(blob_id.into()).await?)
}
/// Sends a transaction to the underlying Provider's client.
pub async fn send_transaction_and_await_commit<T: Transaction>(
&self,
tx: T,
) -> Result<TxStatus> {
#[cfg(feature = "coin-cache")]
let base_asset_id = *self.consensus_parameters().await?.base_asset_id();
#[cfg(feature = "coin-cache")]
self.check_inputs_already_in_cache(&tx.used_coins(&base_asset_id))
.await?;
let tx = self.prepare_transaction_for_sending(tx).await?;
let tx_status = self
.uncached_client()
.submit_and_await_commit(&tx.clone().into())
.await?
.into();
#[cfg(feature = "coin-cache")]
if matches!(
tx_status,
TxStatus::SqueezedOut { .. } | TxStatus::Revert { .. }
) {
self.coins_cache
.lock()
.await
.remove_items(tx.used_coins(&base_asset_id))
}
Ok(tx_status)
}
async fn prepare_transaction_for_sending<T: Transaction>(&self, mut tx: T) -> Result<T> {
let consensus_parameters = self.consensus_parameters().await?;
tx.precompute(&consensus_parameters.chain_id())?;
let chain_info = self.chain_info().await?;
let Header {
height: latest_block_height,
state_transition_bytecode_version: latest_chain_executor_version,
..
} = chain_info.latest_block.header;
if tx.is_using_predicates() {
tx.estimate_predicates(self, Some(latest_chain_executor_version))
.await?;
tx.clone()
.validate_predicates(&consensus_parameters, latest_block_height)?;
}
self.validate_transaction(tx.clone()).await?;
Ok(tx)
}
pub async fn send_transaction<T: Transaction>(&self, tx: T) -> Result<TxId> {
let tx = self.prepare_transaction_for_sending(tx).await?;
self.submit(tx).await
}
pub async fn await_transaction_commit<T: Transaction>(&self, id: TxId) -> Result<TxStatus> {
Ok(self
.uncached_client()
.await_transaction_commit(&id)
.await?
.into())
}
async fn validate_transaction<T: Transaction>(&self, tx: T) -> Result<()> {
let tolerance = 0.0;
let TransactionCost { gas_used, .. } = self
.estimate_transaction_cost(tx.clone(), Some(tolerance), None)
.await?;
tx.validate_gas(gas_used)?;
Ok(())
}
#[cfg(not(feature = "coin-cache"))]
async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
Ok(self.uncached_client().submit(&tx.into()).await?)
}
#[cfg(feature = "coin-cache")]
async fn find_in_cache<'a>(
&self,
coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> Option<((Bech32Address, AssetId), CoinTypeId)> {
let mut locked_cache = self.coins_cache.lock().await;
for (key, ids) in coin_ids {
let items = locked_cache.get_active(key);
if items.is_empty() {
continue;
}
for id in ids {
if items.contains(id) {
return Some((key.clone(), id.clone()));
}
}
}
None
}
#[cfg(feature = "coin-cache")]
async fn check_inputs_already_in_cache<'a>(
&self,
coin_ids: impl IntoIterator<Item = (&'a (Bech32Address, AssetId), &'a Vec<CoinTypeId>)>,
) -> Result<()> {
use fuels_core::types::errors::{transaction, Error};
if let Some(((addr, asset_id), coin_type_id)) = self.find_in_cache(coin_ids).await {
let msg = match coin_type_id {
CoinTypeId::UtxoId(utxo_id) => format!("coin with utxo_id: `{utxo_id:x}`"),
CoinTypeId::Nonce(nonce) => format!("message with nonce: `{nonce}`"),
};
Err(Error::Transaction(transaction::Reason::Validation(
format!("{msg} was submitted recently in a transaction - attempting to spend it again will result in an error. Wallet address: `{addr}`, asset id: `{asset_id}`"),
)))
} else {
Ok(())
}
}
#[cfg(feature = "coin-cache")]
async fn submit<T: Transaction>(&self, tx: T) -> Result<TxId> {
let consensus_parameters = self.consensus_parameters().await?;
let base_asset_id = consensus_parameters.base_asset_id();
let used_utxos = tx.used_coins(base_asset_id);
self.check_inputs_already_in_cache(&used_utxos).await?;
let tx_id = self.uncached_client().submit(&tx.into()).await?;
self.coins_cache.lock().await.insert_multiple(used_utxos);
Ok(tx_id)
}
pub async fn tx_status(&self, tx_id: &TxId) -> Result<TxStatus> {
Ok(self
.uncached_client()
.transaction_status(tx_id)
.await?
.into())
}
pub async fn chain_info(&self) -> Result<ChainInfo> {
Ok(self.uncached_client().chain_info().await?.into())
}
pub async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
self.cached_client.consensus_parameters().await
}
pub async fn node_info(&self) -> Result<NodeInfo> {
Ok(self.uncached_client().node_info().await?.into())
}
pub async fn latest_gas_price(&self) -> Result<LatestGasPrice> {
Ok(self.uncached_client().latest_gas_price().await?)
}
pub async fn estimate_gas_price(&self, block_horizon: u32) -> Result<EstimateGasPrice> {
Ok(self
.uncached_client()
.estimate_gas_price(block_horizon)
.await?)
}
pub async fn dry_run(&self, tx: impl Transaction) -> Result<TxStatus> {
let [tx_status] = self
.uncached_client()
.dry_run(Transactions::new().insert(tx).as_slice())
.await?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.try_into()
.expect("should have only one element");
Ok(tx_status)
}
pub async fn dry_run_multiple(
&self,
transactions: Transactions,
) -> Result<Vec<(TxId, TxStatus)>> {
Ok(self
.uncached_client()
.dry_run(transactions.as_slice())
.await?
.into_iter()
.map(|execution_status| (execution_status.id, execution_status.into()))
.collect())
}
pub async fn dry_run_opt(
&self,
tx: impl Transaction,
utxo_validation: bool,
gas_price: Option<u64>,
) -> Result<TxStatus> {
let [tx_status] = self
.uncached_client()
.dry_run_opt(
Transactions::new().insert(tx).as_slice(),
Some(utxo_validation),
gas_price,
)
.await?
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.try_into()
.expect("should have only one element");
Ok(tx_status)
}
pub async fn dry_run_opt_multiple(
&self,
transactions: Transactions,
utxo_validation: bool,
gas_price: Option<u64>,
) -> Result<Vec<(TxId, TxStatus)>> {
Ok(self
.uncached_client()
.dry_run_opt(transactions.as_slice(), Some(utxo_validation), gas_price)
.await?
.into_iter()
.map(|execution_status| (execution_status.id, execution_status.into()))
.collect())
}
/// Gets all unspent coins owned by address `from`, with asset ID `asset_id`.
pub async fn get_coins(&self, from: &Bech32Address, asset_id: AssetId) -> Result<Vec<Coin>> {
let mut coins: Vec<Coin> = vec![];
let mut cursor = None;
loop {
let response = self
.uncached_client()
.coins(
&from.into(),
Some(&asset_id),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
coins.extend(response.results.into_iter().map(Into::into));
cursor = response.cursor;
}
Ok(coins)
}
async fn request_coins_to_spend(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
let queries = filter.resource_queries();
let consensus_parameters = self.consensus_parameters().await?;
let base_asset_id = *consensus_parameters.base_asset_id();
let res = self
.uncached_client()
.coins_to_spend(
&filter.owner(),
queries.spend_query(base_asset_id),
queries.exclusion_query(),
)
.await?
.into_iter()
.flatten()
.map(CoinType::from)
.collect();
Ok(res)
}
/// Get some spendable coins of asset `asset_id` for address `from` that add up at least to
/// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number
/// of coins (UXTOs) is optimized to prevent dust accumulation.
#[cfg(not(feature = "coin-cache"))]
pub async fn get_spendable_resources(&self, filter: ResourceFilter) -> Result<Vec<CoinType>> {
self.request_coins_to_spend(filter).await
}
/// Get some spendable coins of asset `asset_id` for address `from` that add up at least to
/// amount `amount`. The returned coins (UTXOs) are actual coins that can be spent. The number
/// of coins (UXTOs) is optimized to prevent dust accumulation.
/// Coins that were recently submitted inside a tx will be ignored from the results.
#[cfg(feature = "coin-cache")]
pub async fn get_spendable_resources(
&self,
mut filter: ResourceFilter,
) -> Result<Vec<CoinType>> {
self.extend_filter_with_cached(&mut filter).await?;
self.request_coins_to_spend(filter).await
}
#[cfg(feature = "coin-cache")]
async fn extend_filter_with_cached(&self, filter: &mut ResourceFilter) -> Result<()> {
let consensus_parameters = self.consensus_parameters().await?;
let mut cache = self.coins_cache.lock().await;
let asset_id = filter
.asset_id
.unwrap_or(*consensus_parameters.base_asset_id());
let used_coins = cache.get_active(&(filter.from.clone(), asset_id));
let excluded_utxos = used_coins
.iter()
.filter_map(|coin_id| match coin_id {
CoinTypeId::UtxoId(utxo_id) => Some(utxo_id),
_ => None,
})
.cloned()
.collect::<Vec<_>>();
let excluded_message_nonces = used_coins
.iter()
.filter_map(|coin_id| match coin_id {
CoinTypeId::Nonce(nonce) => Some(nonce),
_ => None,
})
.cloned()
.collect::<Vec<_>>();
filter.excluded_utxos.extend(excluded_utxos);
filter
.excluded_message_nonces
.extend(excluded_message_nonces);
Ok(())
}
/// Get the balance of all spendable coins `asset_id` for address `address`. This is different
/// from getting coins because we are just returning a number (the sum of UTXOs amount) instead
/// of the UTXOs.
pub async fn get_asset_balance(
&self,
address: &Bech32Address,
asset_id: AssetId,
) -> Result<u64> {
Ok(self
.uncached_client()
.balance(&address.into(), Some(&asset_id))
.await?)
}
/// Get the balance of all spendable coins `asset_id` for contract with id `contract_id`.
pub async fn get_contract_asset_balance(
&self,
contract_id: &Bech32ContractId,
asset_id: AssetId,
) -> Result<u64> {
Ok(self
.uncached_client()
.contract_balance(&contract_id.into(), Some(&asset_id))
.await?)
}
/// Get all the spendable balances of all assets for address `address`. This is different from
/// getting the coins because we are only returning the numbers (the sum of UTXOs coins amount
/// for each asset id) and not the UTXOs coins themselves
pub async fn get_balances(&self, address: &Bech32Address) -> Result<HashMap<String, u128>> {
let mut balances = HashMap::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.balances(
&address.into(),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
balances.extend(response.results.into_iter().map(
|Balance {
owner: _,
amount,
asset_id,
}| (asset_id.to_string(), amount),
));
cursor = response.cursor;
}
Ok(balances)
}
/// Get all balances of all assets for the contract with id `contract_id`.
pub async fn get_contract_balances(
&self,
contract_id: &Bech32ContractId,
) -> Result<HashMap<AssetId, u64>> {
let mut contract_balances = HashMap::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.contract_balances(
&contract_id.into(),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
contract_balances.extend(response.results.into_iter().map(
|ContractBalance {
contract: _,
amount,
asset_id,
}| (asset_id, amount),
));
cursor = response.cursor;
}
Ok(contract_balances)
}
pub async fn get_transaction_by_id(&self, tx_id: &TxId) -> Result<Option<TransactionResponse>> {
Ok(self
.uncached_client()
.transaction(tx_id)
.await?
.map(Into::into))
}
pub async fn get_transactions(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
let pr = self.uncached_client().transactions(request).await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
// Get transaction(s) by owner
pub async fn get_transactions_by_owner(
&self,
owner: &Bech32Address,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<TransactionResponse, String>> {
let pr = self
.uncached_client()
.transactions_by_owner(&owner.into(), request)
.await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
pub async fn latest_block_height(&self) -> Result<u32> {
Ok(self.chain_info().await?.latest_block.header.height)
}
pub async fn latest_block_time(&self) -> Result<Option<DateTime<Utc>>> {
Ok(self.chain_info().await?.latest_block.header.time)
}
pub async fn produce_blocks(
&self,
blocks_to_produce: u32,
start_time: Option<DateTime<Utc>>,
) -> Result<u32> {
let start_time = start_time.map(|time| Tai64::from_unix(time.timestamp()).0);
Ok(self
.uncached_client()
.produce_blocks(blocks_to_produce, start_time)
.await?
.into())
}
pub async fn block(&self, block_id: &Bytes32) -> Result<Option<Block>> {
Ok(self
.uncached_client()
.block(block_id)
.await?
.map(Into::into))
}
pub async fn block_by_height(&self, height: BlockHeight) -> Result<Option<Block>> {
Ok(self
.uncached_client()
.block_by_height(height)
.await?
.map(Into::into))
}
// - Get block(s)
pub async fn get_blocks(
&self,
request: PaginationRequest<String>,
) -> Result<PaginatedResult<Block, String>> {
let pr = self.uncached_client().blocks(request).await?;
Ok(PaginatedResult {
cursor: pr.cursor,
results: pr.results.into_iter().map(Into::into).collect(),
has_next_page: pr.has_next_page,
has_previous_page: pr.has_previous_page,
})
}
pub async fn estimate_transaction_cost<T: Transaction>(
&self,
mut tx: T,
tolerance: Option<f64>,
block_horizon: Option<u32>,
) -> Result<TransactionCost> {
let block_horizon = block_horizon.unwrap_or(DEFAULT_GAS_ESTIMATION_BLOCK_HORIZON);
let tolerance = tolerance.unwrap_or(DEFAULT_GAS_ESTIMATION_TOLERANCE);
let EstimateGasPrice { gas_price, .. } = self.estimate_gas_price(block_horizon).await?;
let gas_used = self
.get_gas_used_with_tolerance(tx.clone(), tolerance)
.await?;
if tx.is_using_predicates() {
tx.estimate_predicates(self, None).await?;
}
let transaction_fee = tx
.clone()
.fee_checked_from_tx(&self.consensus_parameters().await?, gas_price)
.expect("Error calculating TransactionFee");
Ok(TransactionCost {
gas_price,
gas_used,
metered_bytes_size: tx.metered_bytes_size() as u64,
total_fee: transaction_fee.max_fee(),
})
}
// Increase estimated gas by the provided tolerance
async fn get_gas_used_with_tolerance<T: Transaction>(
&self,
tx: T,
tolerance: f64,
) -> Result<u64> {
let receipts = self.dry_run_opt(tx, false, None).await?.take_receipts();
let gas_used = self.get_script_gas_used(&receipts);
Ok((gas_used as f64 * (1.0 + tolerance)).ceil() as u64)
}
fn get_script_gas_used(&self, receipts: &[Receipt]) -> u64 {
receipts
.iter()
.rfind(|r| matches!(r, Receipt::ScriptResult { .. }))
.map(|script_result| {
script_result
.gas_used()
.expect("could not retrieve gas used from ScriptResult")
})
.unwrap_or(0)
}
pub async fn get_messages(&self, from: &Bech32Address) -> Result<Vec<Message>> {
let mut messages = Vec::new();
let mut cursor = None;
loop {
let response = self
.uncached_client()
.messages(
Some(&from.into()),
PaginationRequest {
cursor: cursor.clone(),
results: NUM_RESULTS_PER_REQUEST,
direction: PageDirection::Forward,
},
)
.await?;
if response.results.is_empty() {
break;
}
messages.extend(response.results.into_iter().map(Into::into));
cursor = response.cursor;
}
Ok(messages)
}
pub async fn get_message_proof(
&self,
tx_id: &TxId,
nonce: &Nonce,
commit_block_id: Option<&Bytes32>,
commit_block_height: Option<u32>,
) -> Result<MessageProof> {
self.uncached_client()
.message_proof(
tx_id,
nonce,
commit_block_id.map(Into::into),
commit_block_height.map(Into::into),
)
.await
.map(Into::into)
.map_err(Into::into)
}
pub async fn is_user_account(&self, address: impl Into<Bytes32>) -> Result<bool> {
self.uncached_client()
.is_user_account(*address.into())
.await
}
pub fn with_retry_config(mut self, retry_config: RetryConfig) -> Self {
self.uncached_client_mut().set_retry_config(retry_config);
self
}
pub async fn contract_exists(&self, contract_id: &Bech32ContractId) -> Result<bool> {
Ok(self
.uncached_client()
.contract_exists(&contract_id.into())
.await?)
}
fn uncached_client(&self) -> &RetryableClient {
self.cached_client.inner()
}
fn uncached_client_mut(&mut self) -> &mut RetryableClient {
self.cached_client.inner_mut()
}
}
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl DryRunner for Provider {
async fn dry_run(&self, tx: FuelTransaction) -> Result<DryRun> {
let [tx_execution_status] = self
.uncached_client()
.dry_run_opt(&vec![tx], Some(false), Some(0))
.await?
.try_into()
.expect("should have only one element");
let receipts = tx_execution_status.result.receipts();
let script_gas = self.get_script_gas_used(receipts);
let variable_outputs = receipts
.iter()
.filter(
|receipt| matches!(receipt, Receipt::TransferOut { amount, .. } if *amount != 0),
)
.count();
let succeeded = matches!(
tx_execution_status.result,
TransactionExecutionResult::Success { .. }
);
let dry_run = DryRun {
succeeded,
script_gas,
variable_outputs,
};
Ok(dry_run)
}
async fn estimate_gas_price(&self, block_horizon: u32) -> Result<u64> {
Ok(self.estimate_gas_price(block_horizon).await?.gas_price)
}
async fn estimate_predicates(
&self,
tx: &FuelTransaction,
_latest_chain_executor_version: Option<u32>,
) -> Result<FuelTransaction> {
Ok(self.uncached_client().estimate_predicates(tx).await?)
}
async fn consensus_parameters(&self) -> Result<ConsensusParameters> {
Provider::consensus_parameters(self).await
}
}
Below are examples that show how to get the estimated transaction cost from single and multi call transactions.
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The transaction cost estimation can be used to set the gas limit for an actual call, or to show the user the estimated cost.
Note The same estimation interface is available for scripts.
Low-level calls
With low-level calls, you can specify the parameters of your calls at runtime and make indirect calls through other contracts.
Your caller contract should call std::low_level_call::call_with_function_selector, providing:
- target contract ID
- function selector encoded as
Bytes - calldata encoded as
Bytes - whether the calldata contains only a single value argument (e.g. a
u64) std::low_level_call::CallParams
contract;
use std::{
bytes::Bytes,
constants::ZERO_B256,
low_level_call::{
call_with_function_selector,
CallParams,
},
};
abi MyCallerContract {
fn call_low_level_call(
target: ContractId,
function_selector: Bytes,
calldata: Bytes,
);
}
impl MyCallerContract for Contract {
// ANCHOR: low_level_call_contract
fn call_low_level_call(
target: ContractId,
function_selector: Bytes,
calldata: Bytes,
) {
let call_params = CallParams {
coins: 0,
asset_id: AssetId::from(ZERO_B256),
gas: 10_000,
};
call_with_function_selector(target, function_selector, calldata, call_params);
}
// ANCHOR_END: low_level_call_contract
}
On the SDK side, you can construct an encoded function selector using fuels::core::encode_fn_selector, and encoded calldata using the fuels::core::calldata! macro.
E.g. to call the following function on the target contract:
contract;
use std::storage::storage_api::{read, write};
use std::context::msg_amount;
struct MyType {
x: u64,
y: u64,
}
#[allow(dead_code)]
struct Person {
name: str[4],
}
#[allow(dead_code)]
enum State {
A: (),
B: (),
C: (),
}
abi TestContract {
#[storage(write)]
fn initialize_counter(value: u64) -> u64;
#[storage(read, write)]
fn increment_counter(value: u64) -> u64;
#[storage(read)]
fn get_counter() -> u64;
// ANCHOR: low_level_call
#[storage(write)]
fn set_value_multiple_complex(a: MyStruct, b: str[4]);
// ANCHOR_END: low_level_call
#[storage(read)]
fn get_str_value() -> str[4];
#[storage(read)]
fn get_bool_value() -> bool;
#[storage(read)]
fn get_value() -> u64;
fn get(x: u64, y: u64) -> u64;
fn get_alt(x: MyType) -> MyType;
fn get_single(x: u64) -> u64;
fn array_of_structs(p: [Person; 2]) -> [Person; 2];
fn array_of_enums(p: [State; 2]) -> [State; 2];
fn get_array(p: [u64; 2]) -> [u64; 2];
#[payable]
fn get_msg_amount() -> u64;
fn new() -> u64;
}
const COUNTER_KEY = 0x0000000000000000000000000000000000000000000000000000000000000000;
storage {
value: u64 = 0,
value_str: str[4] = __to_str_array("none"),
value_bool: bool = false,
}
pub struct MyStruct {
a: bool,
b: [u64; 3],
}
impl TestContract for Contract {
// ANCHOR: msg_amount
#[payable]
fn get_msg_amount() -> u64 {
msg_amount()
}
// ANCHOR_END: msg_amount
#[storage(write)]
fn initialize_counter(value: u64) -> u64 {
write(COUNTER_KEY, 0, value);
value
}
/// This method will read the counter from storage, increment it
/// and write the incremented value to storage
#[storage(read, write)]
fn increment_counter(value: u64) -> u64 {
let new_value = read::<u64>(COUNTER_KEY, 0).unwrap_or(0) + value;
write(COUNTER_KEY, 0, new_value);
new_value
}
#[storage(read)]
fn get_counter() -> u64 {
read::<u64>(COUNTER_KEY, 0).unwrap_or(0)
}
#[storage(write)]
fn set_value_multiple_complex(a: MyStruct, b: str[4]) {
storage.value.write(a.b[1]);
storage.value_str.write(b);
storage.value_bool.write(a.a);
}
#[storage(read)]
fn get_str_value() -> str[4] {
storage.value_str.read()
}
#[storage(read)]
fn get_bool_value() -> bool {
storage.value_bool.read()
}
#[storage(read)]
fn get_value() -> u64 {
storage.value.read()
}
fn get(x: u64, y: u64) -> u64 {
x + y
}
fn get_alt(t: MyType) -> MyType {
t
}
fn get_single(x: u64) -> u64 {
x
}
fn array_of_structs(p: [Person; 2]) -> [Person; 2] {
p
}
fn array_of_enums(p: [State; 2]) -> [State; 2] {
p
}
fn get_array(p: [u64; 2]) -> [u64; 2] {
p
}
fn new() -> u64 {
12345u64
}
}
you would construct the function selector and the calldata as such, and provide them to the caller contract (like the one above):
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Note: the
calldata!macro uses the defaultEncoderConfigconfiguration under the hood.
Running scripts
You can run a script using its JSON-ABI and the path to its binary file. You can run the scripts with arguments. For this, you have to use the abigen! macro seen previously.
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
Furthermore, if you need to separate submission from value retrieval for any reason, you can do so as follows:
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
Running scripts with transaction policies
The method for passing transaction policies is the same as with contracts. As a reminder, the workflow would look like this:
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
Logs
Script calls provide the same logging functions, decode_logs() and decode_logs_with_type<T>(), as contract calls. As a reminder, the workflow looks like this:
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
Calling contracts from scripts
Scripts use the same interfaces for setting external contracts as contract methods.
Below is an example that uses with_contracts(&[&contract_instance, ...]).
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
And this is an example that uses with_contract_ids(&[&contract_id, ...]).
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
Configurable constants
Same as contracts, you can define configurable constants in scripts which can be changed during the script execution. Here is an example how the constants are defined.
script;
#[allow(dead_code)]
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
configurable {
BOOL: bool = true,
U8: u8 = 8,
U16: u16 = 16,
U32: u32 = 32,
U64: u64 = 63,
U256: u256 = 0x0000000000000000000000000000000000000000000000000000000000000008u256,
B256: b256 = 0x0101010101010101010101010101010101010101010101010101010101010101,
STR_4: str[4] = __to_str_array("fuel"),
TUPLE: (u8, bool) = (8, true),
ARRAY: [u32; 3] = [253, 254, 255],
STRUCT: StructWithGeneric<u8> = StructWithGeneric {
field_1: 8,
field_2: 16,
},
ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}
//U128: u128 = 128, //TODO: add once https://github.com/FuelLabs/sway/issues/5356 is done
fn main() -> (bool, u8, u16, u32, u64, u256, b256, str[4], (u8, bool), [u32; 3], StructWithGeneric<u8>, EnumWithGeneric<bool>) {
(BOOL, U8, U16, U32, U64, U256, B256, STR_4, TUPLE, ARRAY, STRUCT, ENUM)
}
Each configurable constant will get a dedicated with method in the SDK. For example, the constant STR_4 will get the with_STR_4 method which accepts the same type defined in sway. Below is an example where we chain several with methods and execute the script with the new constants.
use fuels::{
core::codec::EncoderConfig,
prelude::*,
types::{Bits256, SizedAsciiString, U256},
};
#[tokio::test]
async fn contract_default_configurables() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
true,
8,
16,
32,
63,
U256::from(8),
Bits256([1; 32]),
"fuel".try_into()?,
(8, true),
[253, 254, 255],
StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
EnumWithGeneric::VariantOne(true),
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn script_default_configurables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_configurables"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let response = script_instance.main().call().await?;
let expected_value = (
true,
8,
16,
32,
63,
U256::from(8),
Bits256([1; 32]),
"fuel".try_into()?,
(8, true),
[253, 254, 255],
StructWithGeneric {
field_1: 8u8,
field_2: 16,
},
EnumWithGeneric::VariantOne(true),
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn contract_configurables() -> Result<()> {
// ANCHOR: contract_configurables
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/configurables/out/release/configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyContractConfigurables::default()
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default().with_configurables(configurables),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
// ANCHOR_END: contract_configurables
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn contract_manual_configurables() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/configurables"
)),
Wallets("wallet")
);
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyContractConfigurables::default()
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let contract_id = Contract::load_from(
"sway/contracts/configurables/out/release/configurables.bin",
LoadConfiguration::default(),
)?
.with_configurables(configurables)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.return_configurables()
.call()
.await?;
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn script_configurables() -> Result<()> {
// ANCHOR: script_configurables
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/script_configurables/out/release/script_configurables.bin";
let instance = MyScript::new(wallet, bin_path);
let str_4: SizedAsciiString<4> = "FUEL".try_into()?;
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyScriptConfigurables::new(EncoderConfig {
max_tokens: 5,
..Default::default()
})
.with_BOOL(false)?
.with_U8(7)?
.with_U16(15)?
.with_U32(31)?
.with_U64(63)?
.with_U256(U256::from(8))?
.with_B256(Bits256([2; 32]))?
.with_STR_4(str_4.clone())?
.with_TUPLE((7, false))?
.with_ARRAY([252, 253, 254])?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let response = instance
.with_configurables(configurables)
.main()
.call()
.await?;
// ANCHOR_END: script_configurables
let expected_value = (
false,
7,
15,
31,
63,
U256::from(8),
Bits256([2; 32]),
str_4,
(7, false),
[252, 253, 254],
new_struct,
new_enum,
);
assert_eq!(response.value, expected_value);
Ok(())
}
#[tokio::test]
async fn configurable_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json"
));
let new_struct = StructWithGeneric {
field_1: 16u8,
field_2: 32,
};
{
let _configurables = MyScriptConfigurables::default()
.with_STRUCT(new_struct.clone())
.expect("no encoder config, it works");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
// Fails when a wrong encoder config is set
let configurables_error = MyScriptConfigurables::new(encoder_config)
.with_STRUCT(new_struct)
.expect_err("should error");
assert!(configurables_error
.to_string()
.contains("token limit `1` reached while encoding. Try increasing it"),)
}
}
Predicates
Predicates, in Sway, are programs that return a Boolean value and do not have any side effects (they are pure). A predicate address can own assets. The predicate address is generated from the compiled byte code and is the same as the P2SH address used in Bitcoin. Users can seamlessly send assets to the predicate address as they do for any other address. To spend the predicate funds, the user has to provide the original byte code of the predicate together with the predicate data. The predicate data will be used when executing the byte code, and the funds can be transferred if the predicate is validated successfully.
Instantiating predicates
Let's consider the following predicate example:
predicate;
fn main(a: u32, b: u64) -> bool {
b == a.as_u64()
}
We will look at a complete example of using the SDK to send and receive funds from a predicate.
First, we set up the wallets and a node instance. The call to the abigen! macro will generate all the types specified in the predicate plus two custom structs:
- an encoder with an
encode_datafunction that will conveniently encode all the arguments of the main function for us. - a configurables struct which holds methods for setting all the configurables mentioned in the predicate
Note: The
abigen!macro will appendEncoderandConfigurablesto the predicate'snamefield. Fox example,name="MyPredicate"will result in two structs calledMyPredicateEncoderandMyPredicateConfigurables.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Once we've compiled our predicate with forc build, we can create a Predicate instance via Predicate::load_from. The resulting data from encode_data can then be set on the loaded predicate.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Next, we lock some assets in this predicate using the first wallet:
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Then we can transfer assets owned by the predicate via the Account trait:
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Configurable constants
Same as contracts and scripts, you can define configurable constants in predicates, which can be changed during the predicate execution. Here is an example of how the constants are defined.
predicate;
impl Eq for StructWithGeneric<u8> {
fn eq(self, other: Self) -> bool {
self.field_1 == other.field_1 && self.field_2 == other.field_2
}
}
impl Eq for EnumWithGeneric<bool> {
fn eq(self, other: Self) -> bool {
match (self, other) {
(EnumWithGeneric::VariantOne, EnumWithGeneric::VariantOne) => true,
(EnumWithGeneric::VariantTwo, EnumWithGeneric::VariantTwo) => true,
_ => false,
}
}
}
// ANCHOR: predicate_configurables
#[allow(dead_code)]
enum EnumWithGeneric<D> {
VariantOne: D,
VariantTwo: (),
}
struct StructWithGeneric<D> {
field_1: D,
field_2: u64,
}
configurable {
BOOL: bool = true,
U8: u8 = 8,
TUPLE: (u8, bool) = (8, true),
ARRAY: [u32; 3] = [253, 254, 255],
STRUCT: StructWithGeneric<u8> = StructWithGeneric {
field_1: 8,
field_2: 16,
},
ENUM: EnumWithGeneric<bool> = EnumWithGeneric::VariantOne(true),
}
fn main(
switch: bool,
u_8: u8,
some_tuple: (u8, bool),
some_array: [u32; 3],
some_struct: StructWithGeneric<u8>,
some_enum: EnumWithGeneric<bool>,
) -> bool {
switch == BOOL && u_8 == U8 && some_tuple.0 == TUPLE.0 && some_tuple.1 == TUPLE.1 && some_array[0] == ARRAY[0] && some_array[1] == ARRAY[1] && some_array[2] == ARRAY[2] && some_struct == STRUCT && some_enum == ENUM
}
// ANCHOR_END: predicate_configurables
Each configurable constant will get a dedicated with method in the SDK. For example, the constant U8 will get the with_U8 method which accepts the same type defined in sway. Below is an example where we chain several with methods and update the predicate with the new constants.
use std::default::Default;
use fuels::{
core::{
codec::{ABIEncoder, EncoderConfig},
traits::Tokenizable,
},
prelude::*,
programs::executable::Executable,
types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output},
};
async fn assert_address_balance(
address: &Bech32Address,
provider: &Provider,
asset_id: AssetId,
amount: u64,
) {
let balance = provider
.get_asset_balance(address, asset_id)
.await
.expect("Could not retrieve balance");
assert_eq!(balance, amount);
}
fn get_test_coins_and_messages(
address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
start_nonce: u64,
) -> (Vec<Coin>, Vec<Message>, AssetId) {
let asset_id = AssetId::zeroed();
let coins = setup_single_asset_coins(address, asset_id, num_coins, amount);
let messages = (0..num_messages)
.map(|i| {
setup_single_message(
&Bech32Address::default(),
address,
amount,
(start_nonce + i).into(),
vec![],
)
})
.collect();
(coins, messages, asset_id)
}
fn get_test_message_w_data(address: &Bech32Address, amount: u64, nonce: u64) -> Message {
setup_single_message(
&Bech32Address::default(),
address,
amount,
nonce.into(),
vec![1, 2, 3],
)
}
// Setup function used to assign coins and messages to a predicate address
// and create a `receiver` wallet
async fn setup_predicate_test(
predicate_address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
) -> Result<(Provider, u64, WalletUnlocked, u64, AssetId, WalletUnlocked)> {
let receiver_num_coins = 1;
let receiver_amount = 1;
let receiver_balance = receiver_num_coins * receiver_amount;
let predicate_balance = (num_coins + num_messages) * amount;
let mut receiver = WalletUnlocked::new_random(None);
let mut extra_wallet = WalletUnlocked::new_random(None);
let (mut coins, messages, asset_id) =
get_test_coins_and_messages(predicate_address, num_coins, num_messages, amount, 0);
coins.extend(setup_single_asset_coins(
receiver.address(),
asset_id,
receiver_num_coins,
receiver_amount,
));
coins.extend(setup_single_asset_coins(
extra_wallet.address(),
AssetId::zeroed(),
10_000,
10_000,
));
coins.extend(setup_single_asset_coins(
predicate_address,
AssetId::from([1u8; 32]),
num_coins,
amount,
));
let provider = setup_test_provider(coins, messages, None, None).await?;
receiver.set_provider(provider.clone());
extra_wallet.set_provider(provider.clone());
Ok((
provider,
predicate_balance,
receiver,
receiver_balance,
asset_id,
extra_wallet,
))
}
#[tokio::test]
async fn transfer_coins_and_messages_to_predicate() -> Result<()> {
let num_coins = 16;
let num_messages = 32;
let amount = 64;
let total_balance = (num_coins + num_messages) * amount;
let mut wallet = WalletUnlocked::new_random(None);
let (coins, messages, asset_id) =
get_test_coins_and_messages(wallet.address(), num_coins, num_messages, amount, 0);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
let predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
wallet
.transfer(
predicate.address(),
total_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has received the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
total_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn spend_predicate_coins_messages_basic() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn pay_with_predicate() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/u64/out/release/u64-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(32768)?;
let mut predicate: Predicate =
Predicate::load_from("sway/types/predicates/u64/out/release/u64.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42) // Build the ABI call
.with_tx_policies(tx_policies)
.call()
.await?;
assert_eq!(42, response.value);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn pay_with_predicate_vector_data() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(12, 30, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(42, response.value);
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn predicate_contract_transfer() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_balances = provider.get_contract_balances(&contract_id).await?;
assert!(contract_balances.is_empty());
let amount = 300;
predicate
.force_transfer_to_contract(
&contract_id,
amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
let contract_balances = predicate
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&AssetId::zeroed()).unwrap();
assert_eq!(*random_asset_balance, 300);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_to_base_layer() -> Result<()> {
use std::str::FromStr;
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount = 1000;
let base_layer_address =
Address::from_str("0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe")?;
let base_layer_address = Bech32Address::from(base_layer_address);
let (tx_id, msg_nonce, _receipts) = predicate
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
// Create the next commit block to be able generate the proof
provider.produce_blocks(1, None).await?;
let proof = predicate
.try_provider()?
.get_message_proof(&tx_id, &msg_nonce, None, Some(2))
.await?;
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_with_signed_resources() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let predicate_num_coins = 4;
let predicate_num_messages = 3;
let predicate_amount = 1000;
let predicate_balance = (predicate_num_coins + predicate_num_messages) * predicate_amount;
let mut wallet = WalletUnlocked::new_random(None);
let wallet_num_coins = 4;
let wallet_num_messages = 3;
let wallet_amount = 1000;
let wallet_balance = (wallet_num_coins + wallet_num_messages) * wallet_amount;
let (mut coins, mut messages, asset_id) = get_test_coins_and_messages(
predicate.address(),
predicate_num_coins,
predicate_num_messages,
predicate_amount,
0,
);
let (wallet_coins, wallet_messages, _) = get_test_coins_and_messages(
wallet.address(),
wallet_num_coins,
wallet_num_messages,
wallet_amount,
predicate_num_messages,
);
coins.extend(wallet_coins);
messages.extend(wallet_messages);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
let mut inputs = wallet
.get_asset_inputs_for_amount(asset_id, wallet_balance, None)
.await?;
let predicate_inputs = predicate
.get_asset_inputs_for_amount(asset_id, predicate_balance, None)
.await?;
inputs.extend(predicate_inputs);
let outputs = vec![Output::change(predicate.address().into(), 0, asset_id)];
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, Default::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance + wallet_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params_with_predicate() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default().with_tip(100);
let call_params_amount = 100;
let call_params = CallParameters::default()
.with_amount(call_params_amount)
.with_asset_id(AssetId::zeroed());
{
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params.clone())?
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate.get_asset_balance(&AssetId::zeroed()).await?,
1800 - expected_fee
);
}
{
let custom_asset = AssetId::from([1u8; 32]);
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.add_custom_asset(custom_asset, 100, Some(Bech32Address::default()))
.call()
.await?;
assert_eq!(predicate.get_asset_balance(&custom_asset).await?, 900);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn diff_asset_predicate_payment() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(28, 14, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1_000_000_000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let call_params = CallParameters::default()
.with_amount(1_000_000)
.with_asset_id(AssetId::from([1u8; 32]));
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.call()
.await?;
Ok(())
}
#[tokio::test]
async fn predicate_default_configurables() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_struct = StructWithGeneric {
field_1: 8u8,
field_2: 16,
};
let new_enum = EnumWithGeneric::VariantOne(true);
let predicate_data = MyPredicateEncoder::default().encode_data(
true,
8,
(8, true),
[253, 254, 255],
new_struct,
new_enum,
)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables() -> Result<()> {
// ANCHOR: predicate_configurables
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data)
.with_configurables(configurables);
// ANCHOR_END: predicate_configurables
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let amount = 1000;
let coins = setup_single_asset_coins(predicate.address(), AssetId::zeroed(), 1, amount);
let message = get_test_message_w_data(predicate.address(), amount, Default::default());
let message_input = Input::resource_predicate(
CoinType::Message(message.clone()),
predicate.code().to_vec(),
predicate.data().to_vec(),
);
let provider = setup_test_provider(coins, vec![message.clone()], None, None).await?;
predicate.set_provider(provider.clone());
let mut tb = ScriptTransactionBuilder::prepare_transfer(
vec![message_input.clone()],
vec![],
TxPolicies::default(),
);
predicate.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
assert_eq!(tx.inputs().len(), 2);
assert_eq!(tx.inputs()[0].message_id().unwrap(), message.message_id());
Ok(())
}
#[tokio::test]
async fn predicate_transfer_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(32, 32)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let mut wallet = WalletUnlocked::new_random(None);
let amount = 5;
let non_base_asset_id = AssetId::new([1; 32]);
// wallet has base and predicate non base asset
let mut coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, amount);
coins.extend(setup_single_asset_coins(
predicate.address(),
non_base_asset_id,
1,
amount,
));
let provider = setup_test_provider(coins, vec![], None, None).await?;
predicate.set_provider(provider.clone());
wallet.set_provider(provider.clone());
let inputs = predicate
.get_asset_inputs_for_amount(non_base_asset_id, amount, None)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let outputs = vec![
Output::change(wallet.address().into(), 0, non_base_asset_id),
Output::change(
wallet.address().into(),
0,
*consensus_parameters.base_asset_id(),
),
];
let mut tb = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_tip(1),
);
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let wallet_balance = wallet.get_asset_balance(&non_base_asset_id).await?;
assert_eq!(wallet_balance, amount);
Ok(())
}
#[tokio::test]
async fn predicate_can_access_manually_added_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
// The predicate has spent the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance - amount_to_send - expected_fee,
)
.await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + amount_to_send,
)
.await;
Ok(())
}
#[tokio::test]
async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, _predicate_balance, receiver, _receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let chain_id = consensus_parameters.chain_id();
let tx_id = tx.id(chain_id);
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
let tx_id_after_witnesses = tx.id(chain_id);
let tx_id_from_provider = provider.send_transaction(tx).await?;
assert_eq!(tx_id, tx_id_after_witnesses);
assert_eq!(tx_id, tx_id_from_provider);
Ok(())
}
#[tokio::test]
async fn predicate_encoder_config_is_applied() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
{
let _encoding_ok = MyPredicateEncoder::default()
.encode_data(4097, 4097)
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let encoding_error = MyPredicateEncoder::new(encoder_config)
.encode_data(4097, 4097)
.expect_err("should fail");
assert!(encoding_error
.to_string()
.contains("token limit `1` reached while encoding"));
}
Ok(())
}
#[tokio::test]
async fn predicate_transfers_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await?;
assert_eq!(predicate.get_asset_balance(&other_asset_id).await?, 0,);
assert_eq!(
receiver.get_asset_balance(&other_asset_id).await?,
send_amount,
);
Ok(())
}
#[tokio::test]
async fn predicate_with_invalid_data_fails() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 100)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
let error_string = predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await
.unwrap_err()
.to_string();
assert!(error_string.contains("PredicateVerificationFailed(Panic(PredicateReturnedNonOne))"));
assert_eq!(receiver.get_asset_balance(&other_asset_id).await?, 0);
Ok(())
}
#[tokio::test]
async fn predicate_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_blobs/out/release/predicate_blobs-abi.json"
));
// ANCHOR: preparing_the_predicate
let configurables = MyPredicateConfigurables::default().with_SECRET_NUMBER(10001)?;
let predicate_data = MyPredicateEncoder::default().encode_data(1, 19)?;
let executable =
Executable::load_from("sway/predicates/predicate_blobs/out/release/predicate_blobs.bin")?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
// ANCHOR_END: preparing_the_predicate
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
// we don't want to pay with the recipient wallet so that we don't affect the assertion we're
// gonna make later on
// ANCHOR: uploading_the_blob
loader.upload_blob(extra_wallet).await?;
predicate.set_provider(provider.clone());
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// ANCHOR_END: uploading_the_blob
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables_in_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let executable = Executable::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
loader.upload_blob(extra_wallet).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
Signatures in predicates example
This is a more involved example where the predicate accepts three signatures and matches them to three predefined public keys. The ec_recover_address function is used to recover the public key from the signatures. If two of the three extracted public keys match the predefined public keys, the funds can be spent. Note that the signature order has to match the order of the predefined public keys.
predicate;
use std::{b512::B512, constants::ZERO_B256, ecr::ec_recover_address, inputs::input_predicate_data};
fn extract_public_key_and_match(signature: B512, expected_public_key: b256) -> u64 {
if let Result::Ok(pub_key_sig) = ec_recover_address(signature, ZERO_B256)
{
if pub_key_sig == Address::from(expected_public_key) {
return 1;
}
}
0
}
fn main(signatures: [B512; 3]) -> bool {
let public_keys = [
0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0,
0x14df7c7e4e662db31fe2763b1734a3d680e7b743516319a49baaa22b2032a857,
0x3ff494fb136978c3125844625dad6baf6e87cdb1328c8a51f35bda5afe72425c,
];
let mut matched_keys = 0;
matched_keys = extract_public_key_and_match(signatures[0], public_keys[0]);
matched_keys = matched_keys + extract_public_key_and_match(signatures[1], public_keys[1]);
matched_keys = matched_keys + extract_public_key_and_match(signatures[2], public_keys[2]);
matched_keys > 1
}
Let's use the SDK to interact with the predicate. First, let's create three wallets with specific keys. Their hashed public keys are already hard-coded in the predicate. Then we create the receiver wallet, which we will use to spend the predicate funds.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Next, let's add some coins, start a provider and connect it with the wallets.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Now we can use the predicate abigen to create a predicate encoder instance for us. To spend the funds now locked in the predicate, we must provide two out of three signatures whose public keys match the ones we defined in the predicate. In this example, the signatures are generated from an array of zeros.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Next, we transfer some assets from a wallet to the created predicate. We also confirm that the funds are indeed transferred.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
We can use the transfer method from the Account trait to transfer the assets. If the predicate data is correct, the receiver wallet will get the funds, and we will verify that the amount is correct.
#[cfg(test)]
mod tests {
use fuels::{
accounts::{predicate::Predicate, Account},
crypto::{Message, SecretKey},
prelude::*,
types::B512,
};
#[tokio::test]
async fn predicate_example() -> Result<()> {
// ANCHOR: predicate_wallets
let secret_key1: SecretKey =
"0x862512a2363db2b3a375c0d4bbbd27172180d89f23f2e259bac850ab02619301".parse()?;
let secret_key2: SecretKey =
"0x37fa81c84ccd547c30c176b118d5cb892bdb113e8e80141f266519422ef9eefd".parse()?;
let secret_key3: SecretKey =
"0x976e5c3fa620092c718d852ca703b6da9e3075b9f2ecb8ed42d9f746bf26aafb".parse()?;
let mut wallet = WalletUnlocked::new_from_private_key(secret_key1, None);
let mut wallet2 = WalletUnlocked::new_from_private_key(secret_key2, None);
let mut wallet3 = WalletUnlocked::new_from_private_key(secret_key3, None);
let mut receiver = WalletUnlocked::new_random(None);
// ANCHOR_END: predicate_wallets
// ANCHOR: predicate_coins
let asset_id = AssetId::zeroed();
let num_coins = 32;
let amount = 64;
let initial_balance = amount * num_coins;
let all_coins = [&wallet, &wallet2, &wallet3, &receiver]
.iter()
.flat_map(|wallet| {
setup_single_asset_coins(wallet.address(), asset_id, num_coins, amount)
})
.collect::<Vec<_>>();
let provider = setup_test_provider(all_coins, vec![], None, None).await?;
[&mut wallet, &mut wallet2, &mut wallet3, &mut receiver]
.iter_mut()
.for_each(|wallet| {
wallet.set_provider(provider.clone());
});
// ANCHOR_END: predicate_coins
let data_to_sign = Message::new([0; 32]);
let signature1: B512 = wallet.sign(data_to_sign).await?.as_ref().try_into()?;
let signature2: B512 = wallet2.sign(data_to_sign).await?.as_ref().try_into()?;
let signature3: B512 = wallet3.sign(data_to_sign).await?.as_ref().try_into()?;
let signatures = [signature1, signature2, signature3];
// ANCHOR: predicate_load
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/signatures/out/release/signatures-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(signatures)?;
let code_path = "../../e2e/sway/predicates/signatures/out/release/signatures.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(provider)
.with_data(predicate_data);
// ANCHOR_END: predicate_load
// ANCHOR: predicate_receive
let amount_to_predicate = 500;
wallet
.transfer(
predicate.address(),
amount_to_predicate,
asset_id,
TxPolicies::default(),
)
.await?;
let predicate_balance = predicate.get_asset_balance(&asset_id).await?;
assert_eq!(predicate_balance, amount_to_predicate);
// ANCHOR_END: predicate_receive
// ANCHOR: predicate_spend
let amount_to_receiver = 300;
predicate
.transfer(
receiver.address(),
amount_to_receiver,
asset_id,
TxPolicies::default(),
)
.await?;
let receiver_balance_after = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(initial_balance + amount_to_receiver, receiver_balance_after);
// ANCHOR_END: predicate_spend
Ok(())
}
#[tokio::test]
async fn predicate_data_example() -> Result<()> {
// ANCHOR: predicate_data_setup
let asset_id = AssetId::zeroed();
let wallets_config = WalletsConfig::new_multiple_assets(
2,
vec![AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 1_000,
}],
);
let wallets = &launch_custom_provider_and_get_wallets(wallets_config, None, None).await?;
let first_wallet = &wallets[0];
let second_wallet = &wallets[1];
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
// ANCHOR_END: predicate_data_setup
// ANCHOR: with_predicate_data
let predicate_data = MyPredicateEncoder::default().encode_data(4096, 4096)?;
let code_path = "../../e2e/sway/predicates/basic_predicate/out/release/basic_predicate.bin";
let predicate: Predicate = Predicate::load_from(code_path)?
.with_provider(first_wallet.try_provider()?.clone())
.with_data(predicate_data);
// ANCHOR_END: with_predicate_data
// ANCHOR: predicate_data_lock_amount
// First wallet transfers amount to predicate.
first_wallet
.transfer(predicate.address(), 500, asset_id, TxPolicies::default())
.await?;
// Check predicate balance.
let balance = predicate.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 500);
// ANCHOR_END: predicate_data_lock_amount
// ANCHOR: predicate_data_unlock
let amount_to_unlock = 300;
predicate
.transfer(
second_wallet.address(),
amount_to_unlock,
asset_id,
TxPolicies::default(),
)
.await?;
// Second wallet balance is updated.
let balance = second_wallet.get_asset_balance(&AssetId::zeroed()).await?;
assert_eq!(balance, 1300);
// ANCHOR_END: predicate_data_unlock
Ok(())
}
}
Pre-uploading code
If you have a script or predicate that is larger than normal or which you plan on calling often, you can pre-upload its code as a blob to the network and run a loader script/predicate instead. The loader can be configured with the script/predicate configurables, so you can change how the script/predicate is configured on each run without having large transactions due to the code duplication.
Scripts
A high level pre-upload:
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
The upload of the blob is handled inside of the convert_into_loader method. If you
want more fine-grained control over it, you can create the script transaction
manually:
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
Predicates
You can prepare a predicate for pre-uploading without doing network requests:
use std::default::Default;
use fuels::{
core::{
codec::{ABIEncoder, EncoderConfig},
traits::Tokenizable,
},
prelude::*,
programs::executable::Executable,
types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output},
};
async fn assert_address_balance(
address: &Bech32Address,
provider: &Provider,
asset_id: AssetId,
amount: u64,
) {
let balance = provider
.get_asset_balance(address, asset_id)
.await
.expect("Could not retrieve balance");
assert_eq!(balance, amount);
}
fn get_test_coins_and_messages(
address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
start_nonce: u64,
) -> (Vec<Coin>, Vec<Message>, AssetId) {
let asset_id = AssetId::zeroed();
let coins = setup_single_asset_coins(address, asset_id, num_coins, amount);
let messages = (0..num_messages)
.map(|i| {
setup_single_message(
&Bech32Address::default(),
address,
amount,
(start_nonce + i).into(),
vec![],
)
})
.collect();
(coins, messages, asset_id)
}
fn get_test_message_w_data(address: &Bech32Address, amount: u64, nonce: u64) -> Message {
setup_single_message(
&Bech32Address::default(),
address,
amount,
nonce.into(),
vec![1, 2, 3],
)
}
// Setup function used to assign coins and messages to a predicate address
// and create a `receiver` wallet
async fn setup_predicate_test(
predicate_address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
) -> Result<(Provider, u64, WalletUnlocked, u64, AssetId, WalletUnlocked)> {
let receiver_num_coins = 1;
let receiver_amount = 1;
let receiver_balance = receiver_num_coins * receiver_amount;
let predicate_balance = (num_coins + num_messages) * amount;
let mut receiver = WalletUnlocked::new_random(None);
let mut extra_wallet = WalletUnlocked::new_random(None);
let (mut coins, messages, asset_id) =
get_test_coins_and_messages(predicate_address, num_coins, num_messages, amount, 0);
coins.extend(setup_single_asset_coins(
receiver.address(),
asset_id,
receiver_num_coins,
receiver_amount,
));
coins.extend(setup_single_asset_coins(
extra_wallet.address(),
AssetId::zeroed(),
10_000,
10_000,
));
coins.extend(setup_single_asset_coins(
predicate_address,
AssetId::from([1u8; 32]),
num_coins,
amount,
));
let provider = setup_test_provider(coins, messages, None, None).await?;
receiver.set_provider(provider.clone());
extra_wallet.set_provider(provider.clone());
Ok((
provider,
predicate_balance,
receiver,
receiver_balance,
asset_id,
extra_wallet,
))
}
#[tokio::test]
async fn transfer_coins_and_messages_to_predicate() -> Result<()> {
let num_coins = 16;
let num_messages = 32;
let amount = 64;
let total_balance = (num_coins + num_messages) * amount;
let mut wallet = WalletUnlocked::new_random(None);
let (coins, messages, asset_id) =
get_test_coins_and_messages(wallet.address(), num_coins, num_messages, amount, 0);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
let predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
wallet
.transfer(
predicate.address(),
total_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has received the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
total_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn spend_predicate_coins_messages_basic() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn pay_with_predicate() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/u64/out/release/u64-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(32768)?;
let mut predicate: Predicate =
Predicate::load_from("sway/types/predicates/u64/out/release/u64.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42) // Build the ABI call
.with_tx_policies(tx_policies)
.call()
.await?;
assert_eq!(42, response.value);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn pay_with_predicate_vector_data() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(12, 30, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(42, response.value);
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn predicate_contract_transfer() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_balances = provider.get_contract_balances(&contract_id).await?;
assert!(contract_balances.is_empty());
let amount = 300;
predicate
.force_transfer_to_contract(
&contract_id,
amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
let contract_balances = predicate
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&AssetId::zeroed()).unwrap();
assert_eq!(*random_asset_balance, 300);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_to_base_layer() -> Result<()> {
use std::str::FromStr;
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount = 1000;
let base_layer_address =
Address::from_str("0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe")?;
let base_layer_address = Bech32Address::from(base_layer_address);
let (tx_id, msg_nonce, _receipts) = predicate
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
// Create the next commit block to be able generate the proof
provider.produce_blocks(1, None).await?;
let proof = predicate
.try_provider()?
.get_message_proof(&tx_id, &msg_nonce, None, Some(2))
.await?;
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_with_signed_resources() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let predicate_num_coins = 4;
let predicate_num_messages = 3;
let predicate_amount = 1000;
let predicate_balance = (predicate_num_coins + predicate_num_messages) * predicate_amount;
let mut wallet = WalletUnlocked::new_random(None);
let wallet_num_coins = 4;
let wallet_num_messages = 3;
let wallet_amount = 1000;
let wallet_balance = (wallet_num_coins + wallet_num_messages) * wallet_amount;
let (mut coins, mut messages, asset_id) = get_test_coins_and_messages(
predicate.address(),
predicate_num_coins,
predicate_num_messages,
predicate_amount,
0,
);
let (wallet_coins, wallet_messages, _) = get_test_coins_and_messages(
wallet.address(),
wallet_num_coins,
wallet_num_messages,
wallet_amount,
predicate_num_messages,
);
coins.extend(wallet_coins);
messages.extend(wallet_messages);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
let mut inputs = wallet
.get_asset_inputs_for_amount(asset_id, wallet_balance, None)
.await?;
let predicate_inputs = predicate
.get_asset_inputs_for_amount(asset_id, predicate_balance, None)
.await?;
inputs.extend(predicate_inputs);
let outputs = vec![Output::change(predicate.address().into(), 0, asset_id)];
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, Default::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance + wallet_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params_with_predicate() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default().with_tip(100);
let call_params_amount = 100;
let call_params = CallParameters::default()
.with_amount(call_params_amount)
.with_asset_id(AssetId::zeroed());
{
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params.clone())?
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate.get_asset_balance(&AssetId::zeroed()).await?,
1800 - expected_fee
);
}
{
let custom_asset = AssetId::from([1u8; 32]);
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.add_custom_asset(custom_asset, 100, Some(Bech32Address::default()))
.call()
.await?;
assert_eq!(predicate.get_asset_balance(&custom_asset).await?, 900);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn diff_asset_predicate_payment() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(28, 14, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1_000_000_000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let call_params = CallParameters::default()
.with_amount(1_000_000)
.with_asset_id(AssetId::from([1u8; 32]));
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.call()
.await?;
Ok(())
}
#[tokio::test]
async fn predicate_default_configurables() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_struct = StructWithGeneric {
field_1: 8u8,
field_2: 16,
};
let new_enum = EnumWithGeneric::VariantOne(true);
let predicate_data = MyPredicateEncoder::default().encode_data(
true,
8,
(8, true),
[253, 254, 255],
new_struct,
new_enum,
)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables() -> Result<()> {
// ANCHOR: predicate_configurables
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data)
.with_configurables(configurables);
// ANCHOR_END: predicate_configurables
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let amount = 1000;
let coins = setup_single_asset_coins(predicate.address(), AssetId::zeroed(), 1, amount);
let message = get_test_message_w_data(predicate.address(), amount, Default::default());
let message_input = Input::resource_predicate(
CoinType::Message(message.clone()),
predicate.code().to_vec(),
predicate.data().to_vec(),
);
let provider = setup_test_provider(coins, vec![message.clone()], None, None).await?;
predicate.set_provider(provider.clone());
let mut tb = ScriptTransactionBuilder::prepare_transfer(
vec![message_input.clone()],
vec![],
TxPolicies::default(),
);
predicate.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
assert_eq!(tx.inputs().len(), 2);
assert_eq!(tx.inputs()[0].message_id().unwrap(), message.message_id());
Ok(())
}
#[tokio::test]
async fn predicate_transfer_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(32, 32)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let mut wallet = WalletUnlocked::new_random(None);
let amount = 5;
let non_base_asset_id = AssetId::new([1; 32]);
// wallet has base and predicate non base asset
let mut coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, amount);
coins.extend(setup_single_asset_coins(
predicate.address(),
non_base_asset_id,
1,
amount,
));
let provider = setup_test_provider(coins, vec![], None, None).await?;
predicate.set_provider(provider.clone());
wallet.set_provider(provider.clone());
let inputs = predicate
.get_asset_inputs_for_amount(non_base_asset_id, amount, None)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let outputs = vec![
Output::change(wallet.address().into(), 0, non_base_asset_id),
Output::change(
wallet.address().into(),
0,
*consensus_parameters.base_asset_id(),
),
];
let mut tb = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_tip(1),
);
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let wallet_balance = wallet.get_asset_balance(&non_base_asset_id).await?;
assert_eq!(wallet_balance, amount);
Ok(())
}
#[tokio::test]
async fn predicate_can_access_manually_added_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
// The predicate has spent the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance - amount_to_send - expected_fee,
)
.await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + amount_to_send,
)
.await;
Ok(())
}
#[tokio::test]
async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, _predicate_balance, receiver, _receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let chain_id = consensus_parameters.chain_id();
let tx_id = tx.id(chain_id);
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
let tx_id_after_witnesses = tx.id(chain_id);
let tx_id_from_provider = provider.send_transaction(tx).await?;
assert_eq!(tx_id, tx_id_after_witnesses);
assert_eq!(tx_id, tx_id_from_provider);
Ok(())
}
#[tokio::test]
async fn predicate_encoder_config_is_applied() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
{
let _encoding_ok = MyPredicateEncoder::default()
.encode_data(4097, 4097)
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let encoding_error = MyPredicateEncoder::new(encoder_config)
.encode_data(4097, 4097)
.expect_err("should fail");
assert!(encoding_error
.to_string()
.contains("token limit `1` reached while encoding"));
}
Ok(())
}
#[tokio::test]
async fn predicate_transfers_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await?;
assert_eq!(predicate.get_asset_balance(&other_asset_id).await?, 0,);
assert_eq!(
receiver.get_asset_balance(&other_asset_id).await?,
send_amount,
);
Ok(())
}
#[tokio::test]
async fn predicate_with_invalid_data_fails() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 100)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
let error_string = predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await
.unwrap_err()
.to_string();
assert!(error_string.contains("PredicateVerificationFailed(Panic(PredicateReturnedNonOne))"));
assert_eq!(receiver.get_asset_balance(&other_asset_id).await?, 0);
Ok(())
}
#[tokio::test]
async fn predicate_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_blobs/out/release/predicate_blobs-abi.json"
));
// ANCHOR: preparing_the_predicate
let configurables = MyPredicateConfigurables::default().with_SECRET_NUMBER(10001)?;
let predicate_data = MyPredicateEncoder::default().encode_data(1, 19)?;
let executable =
Executable::load_from("sway/predicates/predicate_blobs/out/release/predicate_blobs.bin")?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
// ANCHOR_END: preparing_the_predicate
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
// we don't want to pay with the recipient wallet so that we don't affect the assertion we're
// gonna make later on
// ANCHOR: uploading_the_blob
loader.upload_blob(extra_wallet).await?;
predicate.set_provider(provider.clone());
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// ANCHOR_END: uploading_the_blob
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables_in_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let executable = Executable::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
loader.upload_blob(extra_wallet).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
Once you want to execute the predicate, you must beforehand upload the blob containing its code:
use std::default::Default;
use fuels::{
core::{
codec::{ABIEncoder, EncoderConfig},
traits::Tokenizable,
},
prelude::*,
programs::executable::Executable,
types::{coin::Coin, coin_type::CoinType, input::Input, message::Message, output::Output},
};
async fn assert_address_balance(
address: &Bech32Address,
provider: &Provider,
asset_id: AssetId,
amount: u64,
) {
let balance = provider
.get_asset_balance(address, asset_id)
.await
.expect("Could not retrieve balance");
assert_eq!(balance, amount);
}
fn get_test_coins_and_messages(
address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
start_nonce: u64,
) -> (Vec<Coin>, Vec<Message>, AssetId) {
let asset_id = AssetId::zeroed();
let coins = setup_single_asset_coins(address, asset_id, num_coins, amount);
let messages = (0..num_messages)
.map(|i| {
setup_single_message(
&Bech32Address::default(),
address,
amount,
(start_nonce + i).into(),
vec![],
)
})
.collect();
(coins, messages, asset_id)
}
fn get_test_message_w_data(address: &Bech32Address, amount: u64, nonce: u64) -> Message {
setup_single_message(
&Bech32Address::default(),
address,
amount,
nonce.into(),
vec![1, 2, 3],
)
}
// Setup function used to assign coins and messages to a predicate address
// and create a `receiver` wallet
async fn setup_predicate_test(
predicate_address: &Bech32Address,
num_coins: u64,
num_messages: u64,
amount: u64,
) -> Result<(Provider, u64, WalletUnlocked, u64, AssetId, WalletUnlocked)> {
let receiver_num_coins = 1;
let receiver_amount = 1;
let receiver_balance = receiver_num_coins * receiver_amount;
let predicate_balance = (num_coins + num_messages) * amount;
let mut receiver = WalletUnlocked::new_random(None);
let mut extra_wallet = WalletUnlocked::new_random(None);
let (mut coins, messages, asset_id) =
get_test_coins_and_messages(predicate_address, num_coins, num_messages, amount, 0);
coins.extend(setup_single_asset_coins(
receiver.address(),
asset_id,
receiver_num_coins,
receiver_amount,
));
coins.extend(setup_single_asset_coins(
extra_wallet.address(),
AssetId::zeroed(),
10_000,
10_000,
));
coins.extend(setup_single_asset_coins(
predicate_address,
AssetId::from([1u8; 32]),
num_coins,
amount,
));
let provider = setup_test_provider(coins, messages, None, None).await?;
receiver.set_provider(provider.clone());
extra_wallet.set_provider(provider.clone());
Ok((
provider,
predicate_balance,
receiver,
receiver_balance,
asset_id,
extra_wallet,
))
}
#[tokio::test]
async fn transfer_coins_and_messages_to_predicate() -> Result<()> {
let num_coins = 16;
let num_messages = 32;
let amount = 64;
let total_balance = (num_coins + num_messages) * amount;
let mut wallet = WalletUnlocked::new_random(None);
let (coins, messages, asset_id) =
get_test_coins_and_messages(wallet.address(), num_coins, num_messages, amount, 0);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
let predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
wallet
.transfer(
predicate.address(),
total_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has received the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
total_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn spend_predicate_coins_messages_basic() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn pay_with_predicate() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/u64/out/release/u64-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(32768)?;
let mut predicate: Predicate =
Predicate::load_from("sway/types/predicates/u64/out/release/u64.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42) // Build the ABI call
.with_tx_policies(tx_policies)
.call()
.await?;
assert_eq!(42, response.value);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn pay_with_predicate_vector_data() -> Result<()> {
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(12, 30, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000);
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
let consensus_parameters = provider.consensus_parameters().await?;
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
192 - expected_fee
);
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(42, response.value);
assert_eq!(
predicate
.get_asset_balance(consensus_parameters.base_asset_id())
.await?,
191 - expected_fee
);
Ok(())
}
#[tokio::test]
async fn predicate_contract_transfer() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_balances = provider.get_contract_balances(&contract_id).await?;
assert!(contract_balances.is_empty());
let amount = 300;
predicate
.force_transfer_to_contract(
&contract_id,
amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
let contract_balances = predicate
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&AssetId::zeroed()).unwrap();
assert_eq!(*random_asset_balance, 300);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_to_base_layer() -> Result<()> {
use std::str::FromStr;
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 300;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount = 1000;
let base_layer_address =
Address::from_str("0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe")?;
let base_layer_address = Bech32Address::from(base_layer_address);
let (tx_id, msg_nonce, _receipts) = predicate
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
// Create the next commit block to be able generate the proof
provider.produce_blocks(1, None).await?;
let proof = predicate
.try_provider()?
.get_message_proof(&tx_id, &msg_nonce, None, Some(2))
.await?;
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
Ok(())
}
#[tokio::test]
async fn predicate_transfer_with_signed_resources() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(2, 40, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let predicate_num_coins = 4;
let predicate_num_messages = 3;
let predicate_amount = 1000;
let predicate_balance = (predicate_num_coins + predicate_num_messages) * predicate_amount;
let mut wallet = WalletUnlocked::new_random(None);
let wallet_num_coins = 4;
let wallet_num_messages = 3;
let wallet_amount = 1000;
let wallet_balance = (wallet_num_coins + wallet_num_messages) * wallet_amount;
let (mut coins, mut messages, asset_id) = get_test_coins_and_messages(
predicate.address(),
predicate_num_coins,
predicate_num_messages,
predicate_amount,
0,
);
let (wallet_coins, wallet_messages, _) = get_test_coins_and_messages(
wallet.address(),
wallet_num_coins,
wallet_num_messages,
wallet_amount,
predicate_num_messages,
);
coins.extend(wallet_coins);
messages.extend(wallet_messages);
let provider = setup_test_provider(coins, messages, None, None).await?;
wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
let mut inputs = wallet
.get_asset_inputs_for_amount(asset_id, wallet_balance, None)
.await?;
let predicate_inputs = predicate
.get_asset_inputs_for_amount(asset_id, predicate_balance, None)
.await?;
inputs.extend(predicate_inputs);
let outputs = vec![Output::change(predicate.address().into(), 0, asset_id)];
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, Default::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance + wallet_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params_with_predicate() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(22, 20, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let tx_policies = TxPolicies::default().with_tip(100);
let call_params_amount = 100;
let call_params = CallParameters::default()
.with_amount(call_params_amount)
.with_asset_id(AssetId::zeroed());
{
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params.clone())?
.call()
.await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(
predicate.get_asset_balance(&AssetId::zeroed()).await?,
1800 - expected_fee
);
}
{
let custom_asset = AssetId::from([1u8; 32]);
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.add_custom_asset(custom_asset, 100, Some(Bech32Address::default()))
.call()
.await?;
assert_eq!(predicate.get_asset_balance(&custom_asset).await?, 900);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn diff_asset_predicate_payment() -> Result<()> {
use fuels::prelude::*;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
),
Predicate(
name = "MyPredicate",
abi =
"e2e/sway/types/predicates/predicate_vector/out/release/predicate_vector-abi.json"
)
);
let predicate_data = MyPredicateEncoder::default().encode_data(28, 14, vec![2, 4, 42])?;
let mut predicate: Predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(predicate_data);
let num_coins = 1;
let num_messages = 1;
let amount = 1_000_000_000;
let (provider, _predicate_balance, _receiver, _receiver_balance, _asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let contract_id = Contract::load_from(
"./sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), predicate.clone()).methods();
let call_params = CallParameters::default()
.with_amount(1_000_000)
.with_asset_id(AssetId::from([1u8; 32]));
let response = contract_methods
.get_msg_amount()
.call_params(call_params)?
.call()
.await?;
Ok(())
}
#[tokio::test]
async fn predicate_default_configurables() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_struct = StructWithGeneric {
field_1: 8u8,
field_2: 16,
};
let new_enum = EnumWithGeneric::VariantOne(true);
let predicate_data = MyPredicateEncoder::default().encode_data(
true,
8,
(8, true),
[253, 254, 255],
new_struct,
new_enum,
)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables() -> Result<()> {
// ANCHOR: predicate_configurables
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?
.with_data(predicate_data)
.with_configurables(configurables);
// ANCHOR_END: predicate_configurables
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_adjust_fee_persists_message_w_data() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let amount = 1000;
let coins = setup_single_asset_coins(predicate.address(), AssetId::zeroed(), 1, amount);
let message = get_test_message_w_data(predicate.address(), amount, Default::default());
let message_input = Input::resource_predicate(
CoinType::Message(message.clone()),
predicate.code().to_vec(),
predicate.data().to_vec(),
);
let provider = setup_test_provider(coins, vec![message.clone()], None, None).await?;
predicate.set_provider(provider.clone());
let mut tb = ScriptTransactionBuilder::prepare_transfer(
vec![message_input.clone()],
vec![],
TxPolicies::default(),
);
predicate.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
assert_eq!(tx.inputs().len(), 2);
assert_eq!(tx.inputs()[0].message_id().unwrap(), message.message_id());
Ok(())
}
#[tokio::test]
async fn predicate_transfer_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(32, 32)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let mut wallet = WalletUnlocked::new_random(None);
let amount = 5;
let non_base_asset_id = AssetId::new([1; 32]);
// wallet has base and predicate non base asset
let mut coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, amount);
coins.extend(setup_single_asset_coins(
predicate.address(),
non_base_asset_id,
1,
amount,
));
let provider = setup_test_provider(coins, vec![], None, None).await?;
predicate.set_provider(provider.clone());
wallet.set_provider(provider.clone());
let inputs = predicate
.get_asset_inputs_for_amount(non_base_asset_id, amount, None)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let outputs = vec![
Output::change(wallet.address().into(), 0, non_base_asset_id),
Output::change(
wallet.address().into(),
0,
*consensus_parameters.base_asset_id(),
),
];
let mut tb = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_tip(1),
);
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, 0).await?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let wallet_balance = wallet.get_asset_balance(&non_base_asset_id).await?;
assert_eq!(wallet_balance, amount);
Ok(())
}
#[tokio::test]
async fn predicate_can_access_manually_added_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
provider.send_transaction_and_await_commit(tx).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
// The predicate has spent the funds
assert_address_balance(
predicate.address(),
&provider,
asset_id,
predicate_balance - amount_to_send - expected_fee,
)
.await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + amount_to_send,
)
.await;
Ok(())
}
#[tokio::test]
async fn tx_id_not_changed_after_adding_witnesses() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_witnesses/out/release/predicate_witnesses-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 1)?;
let mut predicate: Predicate = Predicate::load_from(
"sway/predicates/predicate_witnesses/out/release/predicate_witnesses.bin",
)?
.with_data(predicate_data);
let num_coins = 4;
let num_messages = 0;
let amount = 16;
let (provider, _predicate_balance, receiver, _receiver_balance, asset_id, _) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
let amount_to_send = 12;
let inputs = predicate
.get_asset_inputs_for_amount(asset_id, amount_to_send, None)
.await?;
let outputs =
predicate.get_asset_outputs_for_amount(receiver.address(), asset_id, amount_to_send);
let mut tx = ScriptTransactionBuilder::prepare_transfer(
inputs,
outputs,
TxPolicies::default().with_witness_limit(32),
)
.build(&provider)
.await?;
let consensus_parameters = provider.consensus_parameters().await?;
let chain_id = consensus_parameters.chain_id();
let tx_id = tx.id(chain_id);
let witness = ABIEncoder::default().encode(&[64u64.into_token()])?; // u64 because this is VM memory
let witness2 = ABIEncoder::default().encode(&[4096u64.into_token()])?;
tx.append_witness(witness.into())?;
tx.append_witness(witness2.into())?;
let tx_id_after_witnesses = tx.id(chain_id);
let tx_id_from_provider = provider.send_transaction(tx).await?;
assert_eq!(tx_id, tx_id_after_witnesses);
assert_eq!(tx_id, tx_id_from_provider);
Ok(())
}
#[tokio::test]
async fn predicate_encoder_config_is_applied() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
{
let _encoding_ok = MyPredicateEncoder::default()
.encode_data(4097, 4097)
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let encoding_error = MyPredicateEncoder::new(encoder_config)
.encode_data(4097, 4097)
.expect_err("should fail");
assert!(encoding_error
.to_string()
.contains("token limit `1` reached while encoding"));
}
Ok(())
}
#[tokio::test]
async fn predicate_transfers_non_base_asset() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(4097, 4097)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await?;
assert_eq!(predicate.get_asset_balance(&other_asset_id).await?, 0,);
assert_eq!(
receiver.get_asset_balance(&other_asset_id).await?,
send_amount,
);
Ok(())
}
#[tokio::test]
async fn predicate_with_invalid_data_fails() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
));
let predicate_data = MyPredicateEncoder::default().encode_data(0, 100)?;
let mut predicate: Predicate =
Predicate::load_from("sway/predicates/basic_predicate/out/release/basic_predicate.bin")?
.with_data(predicate_data);
let num_coins = 4;
let num_message = 6;
let amount = 20;
let (provider, _, receiver, _, _, _) =
setup_predicate_test(predicate.address(), num_coins, num_message, amount).await?;
predicate.set_provider(provider);
let other_asset_id = AssetId::from([1u8; 32]);
let send_amount = num_coins * amount;
let error_string = predicate
.transfer(
receiver.address(),
send_amount,
other_asset_id,
TxPolicies::default(),
)
.await
.unwrap_err()
.to_string();
assert!(error_string.contains("PredicateVerificationFailed(Panic(PredicateReturnedNonOne))"));
assert_eq!(receiver.get_asset_balance(&other_asset_id).await?, 0);
Ok(())
}
#[tokio::test]
async fn predicate_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_blobs/out/release/predicate_blobs-abi.json"
));
// ANCHOR: preparing_the_predicate
let configurables = MyPredicateConfigurables::default().with_SECRET_NUMBER(10001)?;
let predicate_data = MyPredicateEncoder::default().encode_data(1, 19)?;
let executable =
Executable::load_from("sway/predicates/predicate_blobs/out/release/predicate_blobs.bin")?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
// ANCHOR_END: preparing_the_predicate
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
// we don't want to pay with the recipient wallet so that we don't affect the assertion we're
// gonna make later on
// ANCHOR: uploading_the_blob
loader.upload_blob(extra_wallet).await?;
predicate.set_provider(provider.clone());
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// ANCHOR_END: uploading_the_blob
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
#[tokio::test]
async fn predicate_configurables_in_blobs() -> Result<()> {
abigen!(Predicate(
name = "MyPredicate",
abi = "e2e/sway/predicates/predicate_configurables/out/release/predicate_configurables-abi.json"
));
let new_tuple = (16, false);
let new_array = [123, 124, 125];
let new_struct = StructWithGeneric {
field_1: 32u8,
field_2: 64,
};
let new_enum = EnumWithGeneric::VariantTwo;
let configurables = MyPredicateConfigurables::default()
.with_U8(8)?
.with_TUPLE(new_tuple)?
.with_ARRAY(new_array)?
.with_STRUCT(new_struct.clone())?
.with_ENUM(new_enum.clone())?;
let predicate_data = MyPredicateEncoder::default()
.encode_data(true, 8u8, new_tuple, new_array, new_struct, new_enum)?;
let executable = Executable::load_from(
"sway/predicates/predicate_configurables/out/release/predicate_configurables.bin",
)?;
let loader = executable
.convert_to_loader()?
.with_configurables(configurables);
let mut predicate: Predicate = Predicate::from_code(loader.code()).with_data(predicate_data);
let num_coins = 4;
let num_messages = 8;
let amount = 16;
let (provider, predicate_balance, receiver, receiver_balance, asset_id, extra_wallet) =
setup_predicate_test(predicate.address(), num_coins, num_messages, amount).await?;
predicate.set_provider(provider.clone());
loader.upload_blob(extra_wallet).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 1;
predicate
.transfer(
receiver.address(),
predicate_balance - expected_fee,
asset_id,
TxPolicies::default(),
)
.await?;
// The predicate has spent the funds
assert_address_balance(predicate.address(), &provider, asset_id, 0).await;
// Funds were transferred
assert_address_balance(
receiver.address(),
&provider,
asset_id,
receiver_balance + predicate_balance - expected_fee,
)
.await;
Ok(())
}
By pre-uploading the predicate code, you allow for cheaper calls to the predicate from subsequent callers.
Custom transactions
Until now, we have used helpers to create transactions, send them with a provider, and parse the results. However, sometimes we must make custom transactions with specific inputs, outputs, witnesses, etc. In the next chapter, we will show how to use the Rust SDKs transaction builders to accomplish this.
Transaction Builders
The Rust SDK simplifies the creation of Create and Script transactions through two handy builder structs CreateTransactionBuilder, ScriptTransactionBuilder, and the TransactionBuilder trait.
Calling build(&provider) on a builder will result in the corresponding CreateTransaction or ScriptTransaction that can be submitted to the network.
Role of the transaction builders
Note This section contains additional information about the inner workings of the builders. If you are just interested in how to use them, you can skip to the next section.
The builders take on the heavy lifting behind the scenes, offering two standout advantages: handling predicate data offsets and managing witness indexing.
When your transaction involves predicates with dynamic data as inputs, like vectors, the dynamic data contains a pointer pointing to the beginning of the raw data. This pointer's validity hinges on the order of transaction inputs, and any shifting could render it invalid. However, the transaction builders conveniently postpone the resolution of these pointers until you finalize the build process.
Similarly, adding signatures for signed coins requires the signed coin input to hold an index corresponding to the signature in the witnesses array. These indexes can also become invalid if the witness order changes. The Rust SDK again defers the resolution of these indexes until the transaction is finalized. It handles the assignment of correct index witnesses behind the scenes, sparing you the hassle of dealing with indexing intricacies during input definition.
Another added benefit of the builder pattern is that it guards against changes once the transaction is finalized. The transactions resulting from a builder don't permit any changes to the struct that could cause the transaction ID to be modified. This eliminates the headache of calculating and storing a transaction ID for future use, only to accidentally modify the transaction later, resulting in a different transaction ID.
Creating a custom transaction
Here is an example outlining some of the features of the transaction builders.
In this scenario, we have a predicate that holds some bridged asset with ID bridged_asset_id. It releases it's locked assets if the transaction sends ask_amount of the base asset to the receiver address:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Our goal is to create a transaction that will use our hot wallet to transfer the ask_amount to the receiver and then send the unlocked predicate assets to a second wallet that acts as our cold storage.
Let's start by instantiating a builder. Since we don't plan to deploy a contract, the ScriptTransactionBuilder is the appropriate choice:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Next, we need to define transaction inputs of the base asset that sum up to ask_amount. We also need transaction outputs that will assign those assets to the predicate address and thereby unlock it. The methods get_asset_inputs_for_amount and get_asset_outputs_for_amount can help with that. We need to specify the asset ID, the target amount, and the target address:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Let's repeat the same process but this time for transferring the assets held by the predicate to our cold storage:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
We combine all of the inputs and outputs and set them on the builder:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
As we have used coins that require a signature, we have to add the signer to the transaction builder with:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Note The signature is not created until the transaction is finalized with
build(&provider)
We need to do one more thing before we stop thinking about transaction inputs. Executing the transaction also incurs a fee that is paid with the base asset. Our base asset inputs need to be large enough so that the total amount covers the transaction fee and any other operations we are doing. The ViewOnlyAccount trait lets us use adjust_for_fee() for adjusting the transaction inputs if needed to cover the fee. The second argument to adjust_for_fee() is the total amount of the base asset that we expect our transaction to spend regardless of fees. In our case, this is the ask_amount we are transferring to the predicate.
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Note It is recommended to add signers before calling
adjust_for_fee()as the estimation will include the size of the witnesses.
We can also define transaction policies. For example, we can limit the gas price by doing the following:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Our builder needs a signature from the hot wallet to unlock its coins before we call build() and submit the resulting transaction through the provider:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Finally, we verify the transaction succeeded and that the cold storage indeed holds the bridged asset now:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Building a transaction without signatures
If you need to build the transaction without signatures, which is useful when estimating transaction costs or simulations, you can change the build strategy used:
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
Note In contrast to adding signers to a transaction builder, when signing a built transaction, you must ensure that the order of signatures matches the order of signed inputs. Multiple signed inputs with the same owner will have the same witness index.
Custom contract and script calls
When preparing a contract call via CallHandler, the Rust SDK uses a transaction builder in the background. You can fetch this builder and customize it before submitting it to the network. After the transaction is executed successfully, you can use the corresponding CallHandler to generate a call response. The call response can be used to decode return values and logs. Below are examples for both contract and script calls.
Custom contract call
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Custom script call
use std::time::Duration;
use fuel_tx::Output;
use fuels::{
client::{PageDirection, PaginationRequest},
core::{
codec::{DecoderConfig, EncoderConfig},
traits::Tokenizable,
Configurables,
},
prelude::*,
programs::{executable::Executable, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE},
types::{Bits256, Identity},
};
#[tokio::test]
async fn main_function_arguments() -> Result<()> {
// ANCHOR: script_with_arguments
// The abigen is used for the same purpose as with contracts (Rust bindings)
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/scripts/arguments/out/release/arguments.bin";
let script_instance = MyScript::new(wallet, bin_path);
let bim = Bimbam { val: 90 };
let bam = SugarySnack {
twix: 100,
mars: 1000,
};
let result = script_instance.main(bim, bam).call().await?;
let expected = Bimbam { val: 2190 };
assert_eq!(result.value, expected);
// ANCHOR_END: script_with_arguments
Ok(())
}
#[tokio::test]
async fn script_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let a = 4u64;
let b = 2u32;
let estimated_gas_used = script_instance
.main(a, b)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = script_instance.main(a, b).call().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn test_basic_script_with_tx_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "bimbam_script",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "bimbam_script",
wallet = "wallet"
)
);
let a = 1000u64;
let b = 2000u32;
let result = script_instance.main(a, b).call().await?;
assert_eq!(result.value, "hello");
// ANCHOR: script_with_tx_policies
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let result = script_instance
.main(a, b)
.with_tx_policies(tx_policies)
.call()
.await?;
// ANCHOR_END: script_with_tx_policies
assert_eq!(result.value, "hello");
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "transfer_script",
project = "e2e/sway/scripts/transfer_script"
)),
LoadScript(
name = "script_instance",
script = "transfer_script",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?.clone();
let mut receiver = WalletUnlocked::new_random(None);
receiver.set_provider(provider);
let amount = 1000;
let asset_id = AssetId::zeroed();
let script_call = script_instance.main(
amount,
asset_id,
Identity::Address(receiver.address().into()),
);
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let output = Output::change(wallet.address().into(), 0, asset_id);
let _ = script_call
.with_inputs(inputs)
.with_outputs(vec![output])
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
let receiver_balance = receiver.get_asset_balance(&asset_id).await?;
assert_eq!(receiver_balance, amount);
Ok(())
}
#[tokio::test]
async fn test_script_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
let response = script_instance.main(my_struct).call().await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_enum"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_enum = MyEnum::Two;
let response = script_instance.main(my_enum).call().await?;
assert_eq!(response.value, 2);
Ok(())
}
#[tokio::test]
async fn test_script_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_array"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_array: [u64; 4] = [1, 2, 3, 4];
let response = script_instance.main(my_array).call().await?;
assert_eq!(response.value, 10);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_on_script_call() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_needs_custom_decoder"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
{
// Will fail if max_tokens too low
script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 101,
..Default::default()
})
.call()
.await
.expect_err(
"Should fail because return type has more tokens than what is allowed by default",
);
}
{
// When the token limit is bumped should pass
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(response, [0u8; 1000]);
}
Ok(())
}
#[tokio::test]
async fn test_script_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/script_struct"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let my_struct = MyStruct {
number: 42,
boolean: true,
};
// ANCHOR: submit_response_script
let submitted_tx = script_instance.main(my_struct).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_script
assert_eq!(value, 42);
Ok(())
}
#[tokio::test]
async fn test_script_transaction_builder() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.try_provider()?;
// ANCHOR: script_call_tb
let script_call_handler = script_instance.main(1, 2);
let mut tb = script_call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = script_call_handler.get_response_from(tx_status)?;
assert_eq!(response.value, "hello");
// ANCHOR_END: script_call_tb
Ok(())
}
#[tokio::test]
async fn script_encoder_config_is_applied() {
abigen!(Script(
name = "MyScript",
abi = "e2e/sway/scripts/basic_script/out/release/basic_script-abi.json"
));
let wallet = launch_provider_and_get_wallet().await.expect("");
let bin_path = "sway/scripts/basic_script/out/release/basic_script.bin";
let script_instance_without_encoder_config = MyScript::new(wallet.clone(), bin_path);
{
let _encoding_ok = script_instance_without_encoder_config
.main(1, 2)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let script_instance_with_encoder_config =
MyScript::new(wallet.clone(), bin_path).with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
let encoding_error = script_instance_with_encoder_config
.main(1, 2)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode script call arguments: codec: token limit `1` reached while encoding"
));
}
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "MyScript",
project = "e2e/sway/scripts/basic_script"
)),
LoadScript(
name = "script_instance",
script = "MyScript",
wallet = "wallet"
)
);
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let script_instance = script_instance.with_account(no_funds_wallet);
let value = script_instance
.main(1000, 2000)
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value.as_ref(), "hello");
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_builder() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
// ANCHOR: preload_low_level
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular
.convert_to_loader()?
.with_configurables(configurables);
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
// ANCHOR_END: preload_low_level
Ok(())
}
#[tokio::test]
async fn can_be_run_in_blobs_high_level() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let mut my_script = my_script.with_configurables(configurables);
let arg = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
};
let secret = my_script
.convert_into_loader()
.await?
.main(arg)
.call()
.await?
.value;
assert_eq!(secret, 10001);
Ok(())
}
#[tokio::test]
async fn high_level_blob_upload_sets_max_fee_tolerance() -> Result<()> {
let node_config = NodeConfig {
starting_gas_price: 1000000000,
..Default::default()
};
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, u64::MAX);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/script_blobs",
name = "MyScript"
)),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let loader = Executable::from_bytes(std::fs::read(
"sway/scripts/script_blobs/out/release/script_blobs.bin",
)?)
.convert_to_loader()?;
let zero_tolerance_fee = {
let mut tb = BlobTransactionBuilder::default()
.with_blob(loader.blob())
.with_max_fee_estimation_tolerance(0.);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
tx.max_fee().unwrap()
};
let mut my_script = my_script;
my_script.convert_into_loader().await?;
let max_fee_of_sent_blob_tx = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results
.into_iter()
.find_map(|tx| {
if let TransactionType::Blob(blob_transaction) = tx.transaction {
blob_transaction.max_fee()
} else {
None
}
})
.unwrap();
assert_eq!(
max_fee_of_sent_blob_tx,
(zero_tolerance_fee as f32 * (1.0 + DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE)).ceil() as u64,
"the blob upload tx should have had the max fee increased by the default estimation tolerance"
);
Ok(())
}
#[tokio::test]
async fn no_data_section_blob_run() -> Result<()> {
setup_program_test!(
Abigen(Script(
project = "e2e/sway/scripts/empty",
name = "MyScript"
)),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let mut my_script = my_script;
// ANCHOR: preload_high_level
my_script.convert_into_loader().await?.main().call().await?;
// ANCHOR_END: preload_high_level
Ok(())
}
#[tokio::test]
async fn loader_script_calling_loader_proxy() -> Result<()> {
setup_program_test!(
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
),
Contract(name = "MyProxy", project = "e2e/sway/contracts/proxy"),
Script(name = "MyScript", project = "e2e/sway/scripts/script_proxy"),
),
Wallets("wallet"),
LoadScript(name = "my_script", script = "MyScript", wallet = "wallet")
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id.clone(), wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let mut my_script = my_script;
let result = my_script
.convert_into_loader()
.await?
.main(proxy_id.clone())
.with_contract_ids(&[contract_id, proxy_id])
.call()
.await?;
assert!(result.value);
Ok(())
}
#[tokio::test]
async fn loader_can_be_presented_as_a_normal_script_with_shifted_configurables() -> Result<()> {
abigen!(Script(
abi = "e2e/sway/scripts/script_blobs/out/release/script_blobs-abi.json",
name = "MyScript"
));
let binary_path = "./sway/scripts/script_blobs/out/release/script_blobs.bin";
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?.clone();
let regular = Executable::load_from(binary_path)?;
let configurables = MyScriptConfigurables::default().with_SECRET_NUMBER(10001)?;
let loader = regular.clone().convert_to_loader()?;
// The Blob must be uploaded manually, otherwise the script code will revert.
loader.upload_blob(wallet.clone()).await?;
let encoder = fuels::core::codec::ABIEncoder::default();
let token = MyStruct {
field_a: MyEnum::B(99),
field_b: Bits256([17; 32]),
}
.into_token();
let data = encoder.encode(&[token])?;
let configurables: Configurables = configurables.into();
let shifted_configurables = configurables
.with_shifted_offsets(-(regular.data_offset_in_code().unwrap() as i64))
.unwrap()
.with_shifted_offsets(loader.data_offset_in_code() as i64)
.unwrap();
let loader_posing_as_normal_script =
Executable::from_bytes(loader.code()).with_configurables(shifted_configurables);
let mut tb = ScriptTransactionBuilder::default()
.with_script(loader_posing_as_normal_script.code())
.with_script_data(data);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
let response = provider.send_transaction_and_await_commit(tx).await?;
response.check(None)?;
Ok(())
}
Types
The FuelVM and Sway have many internal types. These types have equivalents in the SDK. This section discusses these types, how to use them, and how to convert them.
Bytes32
In Sway and the FuelVM, Bytes32 represents hashes. They hold a 256-bit (32-byte) value. Bytes32 is a wrapper on a 32-sized slice of u8: pub struct Bytes32([u8; 32]);.
These are the main ways of creating a Bytes32:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Bytes32 also implements the fmt module's Debug, Display, LowerHex and UpperHex traits. For example, you can get the display and hex representations with:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
For a full list of implemented methods and traits, see the fuel-types documentation.
Note: In Fuel, there's a special type called
b256, which is similar toBytes32; also used to represent hashes, and it holds a 256-bit value. In Rust, through the SDK, this is represented asBits256(value)wherevalueis a[u8; 32]. If your contract method takes ab256as input, all you need to do is pass aBits256([u8; 32])when calling it from the SDK.
Address
Like Bytes32, Address is a wrapper on [u8; 32] with similar methods and implements the same traits (see fuel-types documentation).
These are the main ways of creating an Address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
ContractId
Like Bytes32, ContractId is a wrapper on [u8; 32] with similar methods and implements the same traits (see fuel-types documentation).
These are the main ways of creating a ContractId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
AssetId
Like Bytes32, AssetId is a wrapper on [u8; 32] with similar methods and implements the same traits (see fuel-types documentation).
These are the main ways of creating an AssetId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Bech32
Bech32Address and Bech32ContractId enable the use of addresses and contract IDs in the bech32 format. They can easily be converted to their counterparts Address and ContractId.
Here are the main ways of creating a Bech32Address, but note that the same applies to Bech32ContractId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Note: when creating a
Bech32AddressfromAddressorBech32ContractIdfromContractIdtheHRP(Human-Readable Part) is set to "fuel" per default.
Structs and enums
The structs and enums you define in your Sway code have equivalents automatically generated by the SDK's abigen! macro.
For instance, if in your Sway code you have a struct called CounterConfig that looks like this:
struct CounterConfig {
dummy: bool,
initial_value: u64,
}
After using the abigen! macro, CounterConfig will be accessible in your Rust file! Here's an example:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
You can freely use your custom types (structs or enums) within this scope. That also means passing custom types to functions and receiving custom types from function calls.
Generics
The Fuel Rust SDK supports both generic enums and generic structs. If you're already familiar with Rust, it's your typical struct MyStruct<T> type of generics support.
For instance, your Sway contract could look like this:
contract;
use std::hash::sha256;
struct SimpleGeneric<T> {
single_generic_param: T,
}
abi MyContract {
fn struct_w_generic(arg1: SimpleGeneric<u64>) -> SimpleGeneric<u64>;
}
impl MyContract for Contract {
fn struct_w_generic(arg1: SimpleGeneric<u64>) -> SimpleGeneric<u64> {
let expected = SimpleGeneric {
single_generic_param: 123u64,
};
assert(arg1.single_generic_param == expected.single_generic_param);
expected
}
}
Your Rust code would look like this:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
Unused generic type parameters
Sway supports unused generic type parameters when declaring structs/enums:
struct SomeStruct<T, K> {
field: u64
}
enum SomeEnum<T, K> {
One: u64
}
If you tried the same in Rust you'd get complaints that T and K must be used or removed. When generating Rust bindings for such types we make use of the PhantomData type. The generated bindings for the above example would look something like this:
struct SomeStruct<T, K> {
pub field: u64,
pub _unused_generic_0: PhantomData<T>
pub _unused_generic_1: PhantomData<K>
}
enum SomeEnum<T, K> {
One(u64),
IgnoreMe(PhantomData<T>, PhantomData<K>)
}
To lessen the impact to developer experience you may use the new method to initialize a structure without bothering with the PhantomDatas.:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
If your struct doesn't have any fields we'll also derive Default. As for enums all PhantomDatas are placed inside a new variant called IgnoreMe which you'll need to ignore in your matches:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
String
The Rust SDK represents Fuel's Strings as SizedAsciiString<LEN>, where the generic parameter LEN is the length of a given string. This abstraction is necessary because all strings in Fuel and Sway are statically-sized, i.e., you must know the size of the string beforehand.
Here's how you can create a simple string using SizedAsciiString:
use std::fmt::{Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::types::errors::{error, Error, Result};
// To be used when interacting with contracts which have string slices in their ABI.
// The FuelVM strings only support ascii characters.
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct AsciiString {
data: String,
}
impl AsciiString {
pub fn new(data: String) -> Result<Self> {
if !data.is_ascii() {
return Err(error!(Other,
"`AsciiString` must be constructed from a string containing only ascii encodable characters. Got: `{data}`"
));
}
Ok(Self { data })
}
pub fn to_trimmed_str(&self) -> &str {
self.data.trim()
}
pub fn to_left_trimmed_str(&self) -> &str {
self.data.trim_start()
}
pub fn to_right_trimmed_str(&self) -> &str {
self.data.trim_end()
}
}
impl TryFrom<&str> for AsciiString {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
Self::new(value.to_owned())
}
}
impl TryFrom<String> for AsciiString {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value)
}
}
impl From<AsciiString> for String {
fn from(ascii_str: AsciiString) -> Self {
ascii_str.data
}
}
impl<const LEN: usize> AsRef<str> for SizedAsciiString<LEN> {
fn as_ref(&self) -> &str {
&self.data
}
}
impl Display for AsciiString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.data)
}
}
impl PartialEq<&str> for AsciiString {
fn eq(&self, other: &&str) -> bool {
self.data == *other
}
}
impl PartialEq<AsciiString> for &str {
fn eq(&self, other: &AsciiString) -> bool {
*self == other.data
}
}
// To be used when interacting with contracts which have strings in their ABI.
// The length of a string is part of its type -- i.e. str[2] is a
// different type from str[3]. The FuelVM strings only support ascii characters.
#[derive(Debug, PartialEq, Clone, Eq, Hash, Default)]
pub struct SizedAsciiString<const LEN: usize> {
data: String,
}
impl<const LEN: usize> SizedAsciiString<LEN> {
pub fn new(data: String) -> Result<Self> {
if !data.is_ascii() {
return Err(error!(Other,
"`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: `{data}`"
));
}
if data.len() != LEN {
return Err(error!(Other,
"`SizedAsciiString<{LEN}>` must be constructed from a `String` of length {LEN}. Got: `{data}`"
));
}
Ok(Self { data })
}
pub fn to_trimmed_str(&self) -> &str {
self.data.trim()
}
pub fn to_left_trimmed_str(&self) -> &str {
self.data.trim_start()
}
pub fn to_right_trimmed_str(&self) -> &str {
self.data.trim_end()
}
/// Pad `data` string with whitespace characters on the right to fit into the `SizedAsciiString`
pub fn new_with_right_whitespace_padding(data: String) -> Result<Self> {
if data.len() > LEN {
return Err(error!(
Other,
"`SizedAsciiString<{LEN}>` cannot be constructed from a string of size {}",
data.len()
));
}
Ok(Self {
data: format!("{:LEN$}", data),
})
}
}
impl<const LEN: usize> TryFrom<&str> for SizedAsciiString<LEN> {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
Self::new(value.to_owned())
}
}
impl<const LEN: usize> TryFrom<String> for SizedAsciiString<LEN> {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value)
}
}
impl<const LEN: usize> From<SizedAsciiString<LEN>> for String {
fn from(sized_ascii_str: SizedAsciiString<LEN>) -> Self {
sized_ascii_str.data
}
}
impl<const LEN: usize> Display for SizedAsciiString<LEN> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.data)
}
}
impl<const LEN: usize> PartialEq<&str> for SizedAsciiString<LEN> {
fn eq(&self, other: &&str) -> bool {
self.data == *other
}
}
impl<const LEN: usize> PartialEq<SizedAsciiString<LEN>> for &str {
fn eq(&self, other: &SizedAsciiString<LEN>) -> bool {
*self == other.data
}
}
impl<const LEN: usize> Serialize for SizedAsciiString<LEN> {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> core::result::Result<S::Ok, S::Error> {
self.data.serialize(serializer)
}
}
impl<'de, const LEN: usize> Deserialize<'de> for SizedAsciiString<LEN> {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> core::result::Result<Self, D::Error> {
let data = String::deserialize(deserializer)?;
Self::new(data).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_ascii_of_correct_length() {
// ANCHOR: string_simple_example
let ascii_data = "abc".to_string();
SizedAsciiString::<3>::new(ascii_data)
.expect("should have succeeded since we gave ascii data of correct length!");
// ANCHOR_END: string_simple_example
}
#[test]
fn refuses_non_ascii() {
let ascii_data = "ab©".to_string();
let err = SizedAsciiString::<3>::new(ascii_data)
.expect_err("should not have succeeded since we gave non ascii data");
let expected_reason = "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: ";
assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason)));
}
#[test]
fn refuses_invalid_len() {
let ascii_data = "abcd".to_string();
let err = SizedAsciiString::<3>::new(ascii_data)
.expect_err("should not have succeeded since we gave data of wrong length");
let expected_reason =
"`SizedAsciiString<3>` must be constructed from a `String` of length 3. Got: `abcd`";
assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason)));
}
// ANCHOR: conversion
#[test]
fn can_be_constructed_from_str_ref() {
let _: SizedAsciiString<3> = "abc".try_into().expect("should have succeeded");
}
#[test]
fn can_be_constructed_from_string() {
let _: SizedAsciiString<3> = "abc".to_string().try_into().expect("should have succeeded");
}
#[test]
fn can_be_converted_into_string() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let str: String = sized_str.into();
assert_eq!(str, "abc");
}
// ANCHOR_END: conversion
#[test]
fn can_be_printed() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
assert_eq!(sized_str.to_string(), "abc");
}
#[test]
fn can_be_compared_w_str_ref() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
assert_eq!(sized_str, "abc");
// and vice-versa
assert_eq!("abc", sized_str);
}
#[test]
fn trim() -> Result<()> {
// Using single whitespaces
let untrimmed = SizedAsciiString::<9>::new(" est abc ".to_string())?;
assert_eq!("est abc ", untrimmed.to_left_trimmed_str());
assert_eq!(" est abc", untrimmed.to_right_trimmed_str());
assert_eq!("est abc", untrimmed.to_trimmed_str());
let padded = // adds 6 whitespaces
SizedAsciiString::<12>::new_with_right_whitespace_padding("victor".to_string())?;
assert_eq!("victor ", padded);
Ok(())
}
#[test]
fn test_can_serialize_sized_ascii() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let serialized = serde_json::to_string(&sized_str).unwrap();
assert_eq!(serialized, "\"abc\"");
}
#[test]
fn test_can_deserialize_sized_ascii() {
let serialized = "\"abc\"";
let deserialized: SizedAsciiString<3> = serde_json::from_str(serialized).unwrap();
assert_eq!(
deserialized,
SizedAsciiString::<3>::new("abc".to_string()).unwrap()
);
}
#[test]
fn test_can_convert_sized_ascii_to_bytes() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let bytes: &[u8] = sized_str.as_ref().as_bytes();
assert_eq!(bytes, &[97, 98, 99]);
}
}
To make working with SizedAsciiStrings easier, you can use try_into() to convert from Rust's String to SizedAsciiString, and you can use into() to convert from SizedAsciiString to Rust's String. Here are a few examples:
use std::fmt::{Debug, Display, Formatter};
use serde::{Deserialize, Serialize};
use crate::types::errors::{error, Error, Result};
// To be used when interacting with contracts which have string slices in their ABI.
// The FuelVM strings only support ascii characters.
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct AsciiString {
data: String,
}
impl AsciiString {
pub fn new(data: String) -> Result<Self> {
if !data.is_ascii() {
return Err(error!(Other,
"`AsciiString` must be constructed from a string containing only ascii encodable characters. Got: `{data}`"
));
}
Ok(Self { data })
}
pub fn to_trimmed_str(&self) -> &str {
self.data.trim()
}
pub fn to_left_trimmed_str(&self) -> &str {
self.data.trim_start()
}
pub fn to_right_trimmed_str(&self) -> &str {
self.data.trim_end()
}
}
impl TryFrom<&str> for AsciiString {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
Self::new(value.to_owned())
}
}
impl TryFrom<String> for AsciiString {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value)
}
}
impl From<AsciiString> for String {
fn from(ascii_str: AsciiString) -> Self {
ascii_str.data
}
}
impl<const LEN: usize> AsRef<str> for SizedAsciiString<LEN> {
fn as_ref(&self) -> &str {
&self.data
}
}
impl Display for AsciiString {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.data)
}
}
impl PartialEq<&str> for AsciiString {
fn eq(&self, other: &&str) -> bool {
self.data == *other
}
}
impl PartialEq<AsciiString> for &str {
fn eq(&self, other: &AsciiString) -> bool {
*self == other.data
}
}
// To be used when interacting with contracts which have strings in their ABI.
// The length of a string is part of its type -- i.e. str[2] is a
// different type from str[3]. The FuelVM strings only support ascii characters.
#[derive(Debug, PartialEq, Clone, Eq, Hash, Default)]
pub struct SizedAsciiString<const LEN: usize> {
data: String,
}
impl<const LEN: usize> SizedAsciiString<LEN> {
pub fn new(data: String) -> Result<Self> {
if !data.is_ascii() {
return Err(error!(Other,
"`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: `{data}`"
));
}
if data.len() != LEN {
return Err(error!(Other,
"`SizedAsciiString<{LEN}>` must be constructed from a `String` of length {LEN}. Got: `{data}`"
));
}
Ok(Self { data })
}
pub fn to_trimmed_str(&self) -> &str {
self.data.trim()
}
pub fn to_left_trimmed_str(&self) -> &str {
self.data.trim_start()
}
pub fn to_right_trimmed_str(&self) -> &str {
self.data.trim_end()
}
/// Pad `data` string with whitespace characters on the right to fit into the `SizedAsciiString`
pub fn new_with_right_whitespace_padding(data: String) -> Result<Self> {
if data.len() > LEN {
return Err(error!(
Other,
"`SizedAsciiString<{LEN}>` cannot be constructed from a string of size {}",
data.len()
));
}
Ok(Self {
data: format!("{:LEN$}", data),
})
}
}
impl<const LEN: usize> TryFrom<&str> for SizedAsciiString<LEN> {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
Self::new(value.to_owned())
}
}
impl<const LEN: usize> TryFrom<String> for SizedAsciiString<LEN> {
type Error = Error;
fn try_from(value: String) -> Result<Self> {
Self::new(value)
}
}
impl<const LEN: usize> From<SizedAsciiString<LEN>> for String {
fn from(sized_ascii_str: SizedAsciiString<LEN>) -> Self {
sized_ascii_str.data
}
}
impl<const LEN: usize> Display for SizedAsciiString<LEN> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.data)
}
}
impl<const LEN: usize> PartialEq<&str> for SizedAsciiString<LEN> {
fn eq(&self, other: &&str) -> bool {
self.data == *other
}
}
impl<const LEN: usize> PartialEq<SizedAsciiString<LEN>> for &str {
fn eq(&self, other: &SizedAsciiString<LEN>) -> bool {
*self == other.data
}
}
impl<const LEN: usize> Serialize for SizedAsciiString<LEN> {
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> core::result::Result<S::Ok, S::Error> {
self.data.serialize(serializer)
}
}
impl<'de, const LEN: usize> Deserialize<'de> for SizedAsciiString<LEN> {
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> core::result::Result<Self, D::Error> {
let data = String::deserialize(deserializer)?;
Self::new(data).map_err(serde::de::Error::custom)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_ascii_of_correct_length() {
// ANCHOR: string_simple_example
let ascii_data = "abc".to_string();
SizedAsciiString::<3>::new(ascii_data)
.expect("should have succeeded since we gave ascii data of correct length!");
// ANCHOR_END: string_simple_example
}
#[test]
fn refuses_non_ascii() {
let ascii_data = "ab©".to_string();
let err = SizedAsciiString::<3>::new(ascii_data)
.expect_err("should not have succeeded since we gave non ascii data");
let expected_reason = "`SizedAsciiString` must be constructed from a `String` containing only ascii encodable characters. Got: ";
assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason)));
}
#[test]
fn refuses_invalid_len() {
let ascii_data = "abcd".to_string();
let err = SizedAsciiString::<3>::new(ascii_data)
.expect_err("should not have succeeded since we gave data of wrong length");
let expected_reason =
"`SizedAsciiString<3>` must be constructed from a `String` of length 3. Got: `abcd`";
assert!(matches!(err, Error::Other(reason) if reason.starts_with(expected_reason)));
}
// ANCHOR: conversion
#[test]
fn can_be_constructed_from_str_ref() {
let _: SizedAsciiString<3> = "abc".try_into().expect("should have succeeded");
}
#[test]
fn can_be_constructed_from_string() {
let _: SizedAsciiString<3> = "abc".to_string().try_into().expect("should have succeeded");
}
#[test]
fn can_be_converted_into_string() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let str: String = sized_str.into();
assert_eq!(str, "abc");
}
// ANCHOR_END: conversion
#[test]
fn can_be_printed() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
assert_eq!(sized_str.to_string(), "abc");
}
#[test]
fn can_be_compared_w_str_ref() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
assert_eq!(sized_str, "abc");
// and vice-versa
assert_eq!("abc", sized_str);
}
#[test]
fn trim() -> Result<()> {
// Using single whitespaces
let untrimmed = SizedAsciiString::<9>::new(" est abc ".to_string())?;
assert_eq!("est abc ", untrimmed.to_left_trimmed_str());
assert_eq!(" est abc", untrimmed.to_right_trimmed_str());
assert_eq!("est abc", untrimmed.to_trimmed_str());
let padded = // adds 6 whitespaces
SizedAsciiString::<12>::new_with_right_whitespace_padding("victor".to_string())?;
assert_eq!("victor ", padded);
Ok(())
}
#[test]
fn test_can_serialize_sized_ascii() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let serialized = serde_json::to_string(&sized_str).unwrap();
assert_eq!(serialized, "\"abc\"");
}
#[test]
fn test_can_deserialize_sized_ascii() {
let serialized = "\"abc\"";
let deserialized: SizedAsciiString<3> = serde_json::from_str(serialized).unwrap();
assert_eq!(
deserialized,
SizedAsciiString::<3>::new("abc".to_string()).unwrap()
);
}
#[test]
fn test_can_convert_sized_ascii_to_bytes() {
let sized_str = SizedAsciiString::<3>::new("abc".to_string()).unwrap();
let bytes: &[u8] = sized_str.as_ref().as_bytes();
assert_eq!(bytes, &[97, 98, 99]);
}
}
If your contract's method takes and returns, for instance, a Sway's str[23]. When using the SDK, this method will take and return a SizedAsciiString<23>.
Bits256
In Fuel, a type called b256 represents hashes and holds a 256-bit value. The Rust SDK represents b256 as Bits256(value) where value is a [u8; 32]. If your contract method takes a b256 as input, you must pass a Bits256([u8; 32]) when calling it from the SDK.
Here's an example:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
If you have a hexadecimal value as a string and wish to convert it to Bits256, you may do so with from_hex_str:
use fuel_types::AssetId;
use fuels_macros::{Parameterize, Tokenizable, TryFrom};
use crate::types::errors::Result;
// A simple wrapper around [u8; 32] representing the `b256` type. Exists
// mainly so that we may differentiate `Parameterize` and `Tokenizable`
// implementations from what otherwise is just an array of 32 u8's.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Bits256(pub [u8; 32]);
impl Bits256 {
/// Returns `Self` with zeroes inside.
pub fn zeroed() -> Self {
Self([0; 32])
}
/// Create a new `Bits256` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let mut bytes = [0u8; 32];
hex::decode_to_slice(hex, &mut bytes as &mut [u8])?;
Ok(Bits256(bytes))
}
}
impl From<AssetId> for Bits256 {
fn from(value: AssetId) -> Self {
Self(value.into())
}
}
// A simple wrapper around [Bits256; 2] representing the `B512` type.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: b512
pub struct B512 {
pub bytes: [Bits256; 2],
}
// ANCHOR_END: b512
impl From<(Bits256, Bits256)> for B512 {
fn from(bits_tuple: (Bits256, Bits256)) -> Self {
B512 {
bytes: [bits_tuple.0, bits_tuple.1],
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: evm_address
pub struct EvmAddress {
// An evm address is only 20 bytes, the first 12 bytes should be set to 0
value: Bits256,
}
// ANCHOR_END: evm_address
impl EvmAddress {
fn new(b256: Bits256) -> Self {
Self {
value: Bits256(Self::clear_12_bytes(b256.0)),
}
}
pub fn value(&self) -> Bits256 {
self.value
}
// sets the leftmost 12 bytes to zero
fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] {
let mut bytes = bytes;
bytes[..12].copy_from_slice(&[0u8; 12]);
bytes
}
}
impl From<Bits256> for EvmAddress {
fn from(b256: Bits256) -> Self {
EvmAddress::new(b256)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
traits::{Parameterize, Tokenizable},
types::{param_types::ParamType, Token},
};
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
assert_eq!(bits256.0, [1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_str_to_bits256
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
// ANCHOR_END: hex_str_to_bits256
assert_eq!(bits256.0, [1u8; 32]);
// ANCHOR_END: from_hex_str
Ok(())
}
#[test]
fn test_param_type_evm_addr() {
assert_eq!(
EvmAddress::param_type(),
ParamType::Struct {
name: "EvmAddress".to_string(),
fields: vec![("value".to_string(), ParamType::B256)],
generics: vec![]
}
);
}
#[test]
fn evm_address_clears_first_12_bytes() -> Result<()> {
let data = [1u8; 32];
let address = EvmAddress::new(Bits256(data));
let expected_data = Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
]);
assert_eq!(address.value(), expected_data);
Ok(())
}
#[test]
fn test_into_token_evm_addr() {
let bits = [1u8; 32];
let evm_address = EvmAddress::from(Bits256(bits));
let token = evm_address.into_token();
let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];
assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)]));
}
}
Bytes
In Fuel, a type called Bytes represents a collection of tightly-packed bytes. The Rust SDK represents Bytes as Bytes(Vec<u8>). Here's an example of using Bytes in a contract call:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
If you have a hexadecimal value as a string and wish to convert it to Bytes, you may do so with from_hex_str:
use crate::types::errors::Result;
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Bytes(pub Vec<u8>);
impl Bytes {
/// Create a new `Bytes` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let bytes = hex::decode(hex)?;
Ok(Bytes(bytes))
}
}
impl From<Bytes> for Vec<u8> {
fn from(bytes: Bytes) -> Vec<u8> {
bytes.0
}
}
impl PartialEq<Vec<u8>> for Bytes {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0 == *other
}
}
impl PartialEq<Bytes> for Vec<u8> {
fn eq(&self, other: &Bytes) -> bool {
*self == other.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: bytes_from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bytes = Bytes::from_hex_str(hex_str)?;
assert_eq!(bytes.0, vec![1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bytes = Bytes::from_hex_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!(bytes.0, vec![1u8; 32]);
// ANCHOR_END: bytes_from_hex_str
Ok(())
}
}
B512
In the Rust SDK, the B512 definition matches the Sway standard library type with the same name and will be converted accordingly when interacting with contracts:
use fuel_types::AssetId;
use fuels_macros::{Parameterize, Tokenizable, TryFrom};
use crate::types::errors::Result;
// A simple wrapper around [u8; 32] representing the `b256` type. Exists
// mainly so that we may differentiate `Parameterize` and `Tokenizable`
// implementations from what otherwise is just an array of 32 u8's.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Bits256(pub [u8; 32]);
impl Bits256 {
/// Returns `Self` with zeroes inside.
pub fn zeroed() -> Self {
Self([0; 32])
}
/// Create a new `Bits256` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let mut bytes = [0u8; 32];
hex::decode_to_slice(hex, &mut bytes as &mut [u8])?;
Ok(Bits256(bytes))
}
}
impl From<AssetId> for Bits256 {
fn from(value: AssetId) -> Self {
Self(value.into())
}
}
// A simple wrapper around [Bits256; 2] representing the `B512` type.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: b512
pub struct B512 {
pub bytes: [Bits256; 2],
}
// ANCHOR_END: b512
impl From<(Bits256, Bits256)> for B512 {
fn from(bits_tuple: (Bits256, Bits256)) -> Self {
B512 {
bytes: [bits_tuple.0, bits_tuple.1],
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: evm_address
pub struct EvmAddress {
// An evm address is only 20 bytes, the first 12 bytes should be set to 0
value: Bits256,
}
// ANCHOR_END: evm_address
impl EvmAddress {
fn new(b256: Bits256) -> Self {
Self {
value: Bits256(Self::clear_12_bytes(b256.0)),
}
}
pub fn value(&self) -> Bits256 {
self.value
}
// sets the leftmost 12 bytes to zero
fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] {
let mut bytes = bytes;
bytes[..12].copy_from_slice(&[0u8; 12]);
bytes
}
}
impl From<Bits256> for EvmAddress {
fn from(b256: Bits256) -> Self {
EvmAddress::new(b256)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
traits::{Parameterize, Tokenizable},
types::{param_types::ParamType, Token},
};
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
assert_eq!(bits256.0, [1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_str_to_bits256
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
// ANCHOR_END: hex_str_to_bits256
assert_eq!(bits256.0, [1u8; 32]);
// ANCHOR_END: from_hex_str
Ok(())
}
#[test]
fn test_param_type_evm_addr() {
assert_eq!(
EvmAddress::param_type(),
ParamType::Struct {
name: "EvmAddress".to_string(),
fields: vec![("value".to_string(), ParamType::B256)],
generics: vec![]
}
);
}
#[test]
fn evm_address_clears_first_12_bytes() -> Result<()> {
let data = [1u8; 32];
let address = EvmAddress::new(Bits256(data));
let expected_data = Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
]);
assert_eq!(address.value(), expected_data);
Ok(())
}
#[test]
fn test_into_token_evm_addr() {
let bits = [1u8; 32];
let evm_address = EvmAddress::from(Bits256(bits));
let token = evm_address.into_token();
let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];
assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)]));
}
}
Here's an example:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
EvmAddress
In the Rust SDK, Ethereum Virtual Machine (EVM) addresses can be represented with the EvmAddress type. Its definition matches with the Sway standard library type with the same name and will be converted accordingly when interacting with contracts:
use fuel_types::AssetId;
use fuels_macros::{Parameterize, Tokenizable, TryFrom};
use crate::types::errors::Result;
// A simple wrapper around [u8; 32] representing the `b256` type. Exists
// mainly so that we may differentiate `Parameterize` and `Tokenizable`
// implementations from what otherwise is just an array of 32 u8's.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Bits256(pub [u8; 32]);
impl Bits256 {
/// Returns `Self` with zeroes inside.
pub fn zeroed() -> Self {
Self([0; 32])
}
/// Create a new `Bits256` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let mut bytes = [0u8; 32];
hex::decode_to_slice(hex, &mut bytes as &mut [u8])?;
Ok(Bits256(bytes))
}
}
impl From<AssetId> for Bits256 {
fn from(value: AssetId) -> Self {
Self(value.into())
}
}
// A simple wrapper around [Bits256; 2] representing the `B512` type.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: b512
pub struct B512 {
pub bytes: [Bits256; 2],
}
// ANCHOR_END: b512
impl From<(Bits256, Bits256)> for B512 {
fn from(bits_tuple: (Bits256, Bits256)) -> Self {
B512 {
bytes: [bits_tuple.0, bits_tuple.1],
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: evm_address
pub struct EvmAddress {
// An evm address is only 20 bytes, the first 12 bytes should be set to 0
value: Bits256,
}
// ANCHOR_END: evm_address
impl EvmAddress {
fn new(b256: Bits256) -> Self {
Self {
value: Bits256(Self::clear_12_bytes(b256.0)),
}
}
pub fn value(&self) -> Bits256 {
self.value
}
// sets the leftmost 12 bytes to zero
fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] {
let mut bytes = bytes;
bytes[..12].copy_from_slice(&[0u8; 12]);
bytes
}
}
impl From<Bits256> for EvmAddress {
fn from(b256: Bits256) -> Self {
EvmAddress::new(b256)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
traits::{Parameterize, Tokenizable},
types::{param_types::ParamType, Token},
};
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
assert_eq!(bits256.0, [1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_str_to_bits256
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
// ANCHOR_END: hex_str_to_bits256
assert_eq!(bits256.0, [1u8; 32]);
// ANCHOR_END: from_hex_str
Ok(())
}
#[test]
fn test_param_type_evm_addr() {
assert_eq!(
EvmAddress::param_type(),
ParamType::Struct {
name: "EvmAddress".to_string(),
fields: vec![("value".to_string(), ParamType::B256)],
generics: vec![]
}
);
}
#[test]
fn evm_address_clears_first_12_bytes() -> Result<()> {
let data = [1u8; 32];
let address = EvmAddress::new(Bits256(data));
let expected_data = Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
]);
assert_eq!(address.value(), expected_data);
Ok(())
}
#[test]
fn test_into_token_evm_addr() {
let bits = [1u8; 32];
let evm_address = EvmAddress::from(Bits256(bits));
let token = evm_address.into_token();
let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];
assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)]));
}
}
Here's an example:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
Note: when creating an
EvmAddressfromBits256, the first 12 bytes will be cleared because an EVM address is only 20 bytes long.
Vectors
Passing in vectors
You can pass a Rust std::vec::Vec into your contract method transparently. The following code calls a Sway contract method which accepts a Vec<SomeStruct<u32>>.
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
You can use a vector just like you would use any other type -- e.g. a [Vec<u32>; 2] or a SomeStruct<Vec<Bits256>> etc.
Returning vectors
Returning vectors from contract methods is supported transparently, with the caveat that you cannot have them nested inside another type. This limitation is temporary.
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
Note: you can still interact with contracts containing methods that return vectors nested inside another type, just not interact with the methods themselves
Converting Types
Below you can find examples for common type conversions:
- Convert Between Native Types
- Convert to
Bytes32 - Convert to
Address - Convert to
ContractId - Convert to
Identity - Convert to
AssetId - Convert to
Bech32 - Convert to
str - Convert to
Bits256 - Convert to
Bytes - Convert to
B512 - Convert to
EvmAddress
Convert Between Native Types
You might want to convert between the native types (Bytes32, Address, ContractId, and AssetId). Because these types are wrappers on [u8; 32], converting is a matter of dereferencing one and instantiating the other using the dereferenced value. Here's an example:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to Bytes32
Convert a [u8; 32] array to Bytes32:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a hex string to Bytes32:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to Address
Convert a [u8; 32] array to an Address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a Bech32 address to an Address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a wallet to an Address:
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[tokio::test]
async fn create_random_wallet() -> Result<()> {
// ANCHOR: create_random_wallet
use fuels::prelude::*;
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create the wallet.
let _wallet = WalletUnlocked::new_random(Some(provider));
// ANCHOR_END: create_random_wallet
Ok(())
}
#[tokio::test]
async fn create_wallet_from_secret_key() -> std::result::Result<(), Box<dyn std::error::Error>>
{
// ANCHOR: create_wallet_from_secret_key
use std::str::FromStr;
use fuels::{crypto::SecretKey, prelude::*};
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Setup the private key.
let secret = SecretKey::from_str(
"5f70feeff1f229e4a95e1056e8b4d80d0b24b565674860cc213bdb07127ce1b1",
)?;
// Create the wallet.
let _wallet = WalletUnlocked::new_from_private_key(secret, Some(provider));
// ANCHOR_END: create_wallet_from_secret_key
Ok(())
}
#[tokio::test]
async fn create_wallet_from_mnemonic() -> Result<()> {
// ANCHOR: create_wallet_from_mnemonic
use fuels::prelude::*;
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let _wallet = WalletUnlocked::new_from_mnemonic_phrase_with_path(
phrase,
Some(provider.clone()),
"m/44'/1179993420'/0'/0/0",
)?;
// Or with the default derivation path
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let expected_address = "fuel17x9kg3k7hqf42396vqenukm4yf59e5k0vj4yunr4mae9zjv9pdjszy098t";
assert_eq!(wallet.address().to_string(), expected_address);
// ANCHOR_END: create_wallet_from_mnemonic
Ok(())
}
#[tokio::test]
async fn create_and_restore_json_wallet() -> Result<()> {
// ANCHOR: create_and_restore_json_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let mut rng = rand::thread_rng();
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let password = "my_master_password";
// Create a wallet to be stored in the keystore.
let (_wallet, uuid) =
WalletUnlocked::new_from_keystore(&dir, &mut rng, password, Some(provider.clone()))?;
let path = dir.join(uuid);
let _recovered_wallet = WalletUnlocked::load_keystore(path, password, Some(provider))?;
// ANCHOR_END: create_and_restore_json_wallet
Ok(())
}
#[tokio::test]
async fn create_and_store_mnemonic_wallet() -> Result<()> {
// ANCHOR: create_and_store_mnemonic_wallet
use fuels::prelude::*;
let dir = std::env::temp_dir();
let phrase =
"oblige salon price punch saddle immune slogan rare snap desert retire surprise";
// Use the test helper to setup a test provider.
let provider = setup_test_provider(vec![], vec![], None, None).await?;
// Create first account from mnemonic phrase.
let wallet = WalletUnlocked::new_from_mnemonic_phrase(phrase, Some(provider))?;
let password = "my_master_password";
// Encrypts and stores it on disk. Can be recovered using `Wallet::load_keystore`.
let _uuid = wallet.encrypt(&dir, password)?;
// ANCHOR_END: create_and_store_mnemonic_wallet
Ok(())
}
#[tokio::test]
async fn wallet_transfer() -> Result<()> {
// ANCHOR: wallet_transfer
use fuels::prelude::*;
// Setup 2 test wallets with 1 coin each
let num_wallets = 2;
let coins_per_wallet = 1;
let coin_amount = 2;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(num_wallets), Some(coins_per_wallet), Some(coin_amount)),
None,
None,
)
.await?;
// Transfer the base asset with amount 1 from wallet 1 to wallet 2
let transfer_amount = 1;
let asset_id = Default::default();
let (_tx_id, _receipts) = wallets[0]
.transfer(
wallets[1].address(),
transfer_amount,
asset_id,
TxPolicies::default(),
)
.await?;
let wallet_2_final_coins = wallets[1].get_coins(AssetId::zeroed()).await?;
// Check that wallet 2 now has 2 coins
assert_eq!(wallet_2_final_coins.len(), 2);
// ANCHOR_END: wallet_transfer
Ok(())
}
#[tokio::test]
async fn wallet_contract_transfer() -> Result<()> {
use fuels::prelude::*;
use rand::Fill;
let mut rng = rand::thread_rng();
let base_asset = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: 1000,
};
let mut random_asset_id = AssetId::zeroed();
random_asset_id.try_fill(&mut rng).unwrap();
let random_asset = AssetConfig {
id: random_asset_id,
num_coins: 3,
coin_amount: 100,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![random_asset, base_asset]);
let wallet = launch_custom_provider_and_get_wallets(wallet_config, None, None)
.await?
.pop()
.unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: wallet_contract_transfer
// Check the current balance of the contract with id 'contract_id'
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert!(contract_balances.is_empty());
// Transfer an amount of 300 to the contract
let amount = 300;
let asset_id = random_asset_id;
let (_tx_id, _receipts) = wallet
.force_transfer_to_contract(&contract_id, amount, asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = wallet
.try_provider()?
.get_contract_balances(&contract_id)
.await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(&random_asset_id).unwrap();
assert_eq!(*random_asset_balance, 300);
// ANCHOR_END: wallet_contract_transfer
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_multiple_wallets() -> Result<()> {
// ANCHOR: multiple_wallets_helper
use fuels::prelude::*;
// This helper will launch a local node and provide 10 test wallets linked to it.
// The initial balance defaults to 1 coin per wallet with an amount of 1_000_000_000
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
// ANCHOR_END: multiple_wallets_helper
// ANCHOR: setup_5_wallets
let num_wallets = 5;
let coins_per_wallet = 3;
let amount_per_coin = 100;
let config = WalletsConfig::new(
Some(num_wallets),
Some(coins_per_wallet),
Some(amount_per_coin),
);
// Launches a local node and provides test wallets as specified by the config
let wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
// ANCHOR_END: setup_5_wallets
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_multiple_assets() -> Result<()> {
// ANCHOR: multiple_assets_wallet
// ANCHOR: multiple_assets_coins
use fuels::prelude::*;
let mut wallet = WalletUnlocked::new_random(None);
let num_assets = 5; // 5 different assets
let coins_per_asset = 10; // Per asset id, 10 coins in the wallet
let amount_per_coin = 15; // For each coin (UTXO) of the asset, amount of 15
let (coins, asset_ids) = setup_multiple_assets_coins(
wallet.address(),
num_assets,
coins_per_asset,
amount_per_coin,
);
// ANCHOR_END: multiple_assets_coins
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: multiple_assets_wallet
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn setup_wallet_custom_assets() -> std::result::Result<(), Box<dyn std::error::Error>> {
// ANCHOR: custom_assets_wallet
use fuels::prelude::*;
use rand::Fill;
let mut wallet = WalletUnlocked::new_random(None);
let mut rng = rand::thread_rng();
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 2,
coin_amount: 4,
};
let mut asset_id_1 = AssetId::zeroed();
asset_id_1.try_fill(&mut rng)?;
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 6,
coin_amount: 8,
};
let mut asset_id_2 = AssetId::zeroed();
asset_id_2.try_fill(&mut rng)?;
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 10,
coin_amount: 12,
};
let assets = vec![asset_base, asset_1, asset_2];
let coins = setup_custom_assets_coins(wallet.address(), &assets);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider);
// ANCHOR_END: custom_assets_wallet
// ANCHOR: custom_assets_wallet_short
let num_wallets = 1;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
// ANCHOR_END: custom_assets_wallet_short
// ANCHOR: wallet_to_address
let wallet_unlocked = WalletUnlocked::new_random(None);
let address: Address = wallet_unlocked.address().into();
// ANCHOR_END: wallet_to_address
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_balances() -> Result<()> {
use std::collections::HashMap;
use fuels::{
prelude::{launch_provider_and_get_wallet, DEFAULT_COIN_AMOUNT, DEFAULT_NUM_COINS},
types::AssetId,
};
let wallet = launch_provider_and_get_wallet().await?;
// ANCHOR: get_asset_balance
let asset_id = AssetId::zeroed();
let balance: u64 = wallet.get_asset_balance(&asset_id).await?;
// ANCHOR_END: get_asset_balance
// ANCHOR: get_balances
let balances: HashMap<String, u128> = wallet.get_balances().await?;
// ANCHOR_END: get_balances
// ANCHOR: get_balance_hashmap
let asset_balance = balances.get(&asset_id.to_string()).unwrap();
// ANCHOR_END: get_balance_hashmap
assert_eq!(
*asset_balance,
(DEFAULT_COIN_AMOUNT * DEFAULT_NUM_COINS) as u128
);
Ok(())
}
#[tokio::test]
async fn wallet_transfer_to_base_layer() -> Result<()> {
// ANCHOR: wallet_withdraw_to_base
use std::str::FromStr;
use fuels::prelude::*;
let wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), None, None),
None,
None,
)
.await?;
let wallet = wallets.first().unwrap();
let amount = 1000;
let base_layer_address = Address::from_str(
"0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
)?;
let base_layer_address = Bech32Address::from(base_layer_address);
// Transfer an amount of 1000 to the specified base layer address
let (tx_id, msg_id, _receipts) = wallet
.withdraw_to_base_layer(&base_layer_address, amount, TxPolicies::default())
.await?;
let _block_height = wallet.try_provider()?.produce_blocks(1, None).await?;
// Retrieve a message proof from the provider
let proof = wallet
.try_provider()?
.get_message_proof(&tx_id, &msg_id, None, Some(2))
.await?;
// Verify the amount and recipient
assert_eq!(proof.amount, amount);
assert_eq!(proof.recipient, base_layer_address);
// ANCHOR_END: wallet_withdraw_to_base
Ok(())
}
}
Convert a hex string to an Address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to ContractId
Convert a [u8; 32] array to ContractId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a hex string to a ContractId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a contract instance to a ContractId:
use fuels::{
core::codec::DecoderConfig,
prelude::*,
types::{errors::transaction::Reason, AsciiString, Bits256, SizedAsciiString},
};
#[tokio::test]
async fn test_parse_logged_variables() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: produce_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_variables().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_bits256 = response.decode_logs_with_type::<Bits256>()?;
let log_string = response.decode_logs_with_type::<SizedAsciiString<4>>()?;
let log_array = response.decode_logs_with_type::<[u8; 3]>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
assert_eq!(log_u64, vec![64]);
assert_eq!(log_bits256, vec![expected_bits256]);
assert_eq!(log_string, vec!["Fuel"]);
assert_eq!(log_array, vec![[1, 2, 3]]);
// ANCHOR_END: produce_logs
Ok(())
}
#[tokio::test]
async fn test_parse_logs_values() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_values().call().await?;
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_parse_logs_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_custom_types().call().await?;
let log_test_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_test_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_tuple = response.decode_logs_with_type::<(TestStruct, TestEnum)>()?;
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
assert_eq!(log_test_struct, vec![expected_struct.clone()]);
assert_eq!(log_test_enum, vec![expected_enum.clone()]);
assert_eq!(log_tuple, vec![(expected_struct, expected_enum)]);
Ok(())
}
#[tokio::test]
async fn test_parse_logs_generic_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_logs_generic_types().call().await?;
let log_struct = response.decode_logs_with_type::<StructWithGeneric<[_; 3]>>()?;
let log_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_struct_nested =
response.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<[_; 3]>>>()?;
let log_struct_deeply_nested = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<[_; 3]>>,
>>()?;
let l = [1u8, 2u8, 3u8];
let expected_struct = StructWithGeneric {
field_1: l,
field_2: 64,
};
let expected_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_struct_nested, vec![expected_nested_struct]);
assert_eq!(
log_struct_deeply_nested,
vec![expected_deeply_nested_struct]
);
Ok(())
}
#[tokio::test]
async fn test_decode_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR: decode_logs
let contract_methods = contract_instance.methods();
let response = contract_methods.produce_multiple_logs().call().await?;
let logs = response.decode_logs();
// ANCHOR_END: decode_logs
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_generic_struct:?}"),
];
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
async fn test_decode_logs_with_no_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let logs = contract_methods
.initialize_counter(42)
.call()
.await?
.decode_logs();
assert!(logs.filter_succeeded().is_empty());
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.produce_logs_values();
let call_handler_2 = contract_methods.produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_log_multiple_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/logs/contract_logs"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_instance2.methods().produce_logs_variables();
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!(
"{:?}",
Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161,
16, 60, 239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
])
),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs"),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs"
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance2",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id.clone())
.with_contracts(&[&contract_instance]);
let call_handler_2 = contract_caller_instance2
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = multi_call_handler.call::<((), ())>().await?.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
fn assert_revert_containing_msg(msg: &str, error: Error) {
assert!(matches!(error, Error::Transaction(Reason::Reverted { .. })));
if let Error::Transaction(Reason::Reverted { reason, .. }) = error {
assert!(
reason.contains(msg),
"message: \"{msg}\" not contained in reason: \"{reason}\""
);
}
}
#[tokio::test]
async fn test_revert_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
($method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method()
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(require_primitive, call, "42");
reverts_with_msg!(require_primitive, simulate, "42");
reverts_with_msg!(require_string, call, "fuel");
reverts_with_msg!(require_string, simulate, "fuel");
reverts_with_msg!(require_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
require_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(require_with_additional_logs, call, "64");
reverts_with_msg!(require_with_additional_logs, simulate, "64");
}
{
reverts_with_msg!(rev_w_log_primitive, call, "42");
reverts_with_msg!(rev_w_log_primitive, simulate, "42");
reverts_with_msg!(rev_w_log_string, call, "fuel");
reverts_with_msg!(rev_w_log_string, simulate, "fuel");
reverts_with_msg!(rev_w_log_custom_generic, call, "StructDeeplyNestedGeneric");
reverts_with_msg!(
rev_w_log_custom_generic,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_single_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
async fn test_multi_call_revert_logs_multi_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertLogsContract",
project = "e2e/sway/contracts/revert_logs"
)),
Deploy(
name = "contract_instance",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "RevertLogsContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let contract_methods2 = contract_instance2.methods();
// The output of the error depends on the order of the contract
// handlers as the script returns the first revert it finds.
{
let call_handler_1 = contract_methods.require_string();
let call_handler_2 = contract_methods2.rev_w_log_custom_generic();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("fuel", error);
}
{
let call_handler_1 = contract_methods2.require_custom_generic();
let call_handler_2 = contract_methods.rev_w_log_string();
let mut multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.simulate::<((), ())>(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("StructDeeplyNestedGeneric", error);
}
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_decode_logs() -> Result<()> {
// ANCHOR: script_logs
abigen!(Script(
name = "LogScript",
abi = "e2e/sway/logs/script_logs/out/release/script_logs-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let bin_path = "sway/logs/script_logs/out/release/script_logs.bin";
let instance = LogScript::new(wallet.clone(), bin_path);
let response = instance.main().call().await?;
let logs = response.decode_logs();
let log_u64 = response.decode_logs_with_type::<u64>()?;
// ANCHOR_END: script_logs
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_tuple = (expected_struct.clone(), expected_enum.clone());
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let expected_logs: Vec<String> = vec![
format!("{:?}", 128u64),
format!("{:?}", 32u32),
format!("{:?}", 16u16),
format!("{:?}", 8u8),
format!("{:?}", 64u64),
format!("{expected_bits256:?}"),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
format!("{expected_struct:?}"),
format!("{expected_enum:?}"),
format!("{expected_tuple:?}"),
format!("{expected_generic_struct:?}"),
format!("{expected_generic_enum:?}"),
format!("{expected_nested_struct:?}"),
format!("{expected_deeply_nested_struct:?}"),
];
assert_eq!(logs.filter_succeeded(), expected_logs);
Ok(())
}
#[tokio::test]
async fn test_contract_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Contract(
name = "ContractCaller",
project = "e2e/sway/logs/contract_with_contract_logs",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/logs/contract_logs/out/release/contract_logs.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let expected_logs: Vec<String> = vec![
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
];
let logs = contract_caller_instance
.methods()
.logs_from_external_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?
.decode_logs();
assert_eq!(expected_logs, logs.filter_succeeded());
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn test_script_logs_with_contract_logs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(name = "MyContract", project = "e2e/sway/logs/contract_logs",),
Script(
name = "LogScript",
project = "e2e/sway/logs/script_with_contract_logs"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let expected_num_contract_logs = 4;
let expected_script_logs: Vec<String> = vec![
// Contract logs
format!("{:?}", 64),
format!("{:?}", 32),
format!("{:?}", 16),
format!("{:?}", 8),
// Script logs
format!("{:?}", true),
format!("{:?}", 42),
format!("{:?}", SizedAsciiString::<4>::new("Fuel".to_string())?),
format!("{:?}", [1, 2, 3]),
];
// ANCHOR: instance_to_contract_id
let contract_id: ContractId = contract_instance.id().into();
// ANCHOR_END: instance_to_contract_id
// ANCHOR: external_contract_ids
let response = script_instance
.main(contract_id)
.with_contract_ids(&[contract_id.into()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
// ANCHOR: external_contract
let response = script_instance
.main(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
{
let num_contract_logs = response
.receipts
.iter()
.filter(|receipt| matches!(receipt, Receipt::LogData { id, .. } | Receipt::Log { id, .. } if *id == contract_id))
.count();
assert_eq!(num_contract_logs, expected_num_contract_logs);
}
{
let logs = response.decode_logs();
assert_eq!(logs.filter_succeeded(), expected_script_logs);
}
Ok(())
}
#[tokio::test]
async fn test_script_decode_logs_with_type() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
let l = [1u8, 2u8, 3u8];
let expected_bits256 = Bits256([
239, 134, 175, 169, 105, 108, 240, 220, 99, 133, 226, 196, 7, 166, 225, 89, 161, 16, 60,
239, 183, 226, 174, 6, 54, 251, 51, 211, 203, 42, 158, 74,
]);
let expected_struct = TestStruct {
field_1: true,
field_2: expected_bits256,
field_3: 64,
};
let expected_enum = TestEnum::VariantTwo;
let expected_generic_struct = StructWithGeneric {
field_1: expected_struct.clone(),
field_2: 64,
};
let expected_generic_enum = EnumWithGeneric::VariantOne(l);
let expected_nested_struct = StructWithNestedGeneric {
field_1: expected_generic_struct.clone(),
field_2: 64,
};
let expected_deeply_nested_struct = StructDeeplyNestedGeneric {
field_1: expected_nested_struct.clone(),
field_2: 64,
};
let log_u64 = response.decode_logs_with_type::<u64>()?;
let log_u32 = response.decode_logs_with_type::<u32>()?;
let log_u16 = response.decode_logs_with_type::<u16>()?;
let log_u8 = response.decode_logs_with_type::<u8>()?;
let log_struct = response.decode_logs_with_type::<TestStruct>()?;
let log_enum = response.decode_logs_with_type::<TestEnum>()?;
let log_generic_struct = response.decode_logs_with_type::<StructWithGeneric<TestStruct>>()?;
let log_generic_enum = response.decode_logs_with_type::<EnumWithGeneric<[_; 3]>>()?;
let log_nested_struct = response
.decode_logs_with_type::<StructWithNestedGeneric<StructWithGeneric<TestStruct>>>()?;
let log_deeply_nested_struct = response.decode_logs_with_type::<StructDeeplyNestedGeneric<
StructWithNestedGeneric<StructWithGeneric<TestStruct>>,
>>()?;
// try to retrieve non existent log
let log_nonexistent = response.decode_logs_with_type::<bool>()?;
assert_eq!(log_u64, vec![128, 64]);
assert_eq!(log_u32, vec![32]);
assert_eq!(log_u16, vec![16]);
assert_eq!(log_u8, vec![8]);
assert_eq!(log_struct, vec![expected_struct]);
assert_eq!(log_enum, vec![expected_enum]);
assert_eq!(log_generic_struct, vec![expected_generic_struct]);
assert_eq!(log_generic_enum, vec![expected_generic_enum]);
assert_eq!(log_nested_struct, vec![expected_nested_struct]);
assert_eq!(
log_deeply_nested_struct,
vec![expected_deeply_nested_struct]
);
assert!(log_nonexistent.is_empty());
Ok(())
}
#[tokio::test]
async fn test_script_require_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_revert_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(MatchEnum::RequirePrimitive, call, "42");
reverts_with_msg!(MatchEnum::RequirePrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RequireString, call, "fuel");
reverts_with_msg!(MatchEnum::RequireString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RequireCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, call, "64");
reverts_with_msg!(MatchEnum::RequireWithAdditionalLogs, simulate, "64");
}
{
reverts_with_msg!(MatchEnum::RevWLogPrimitive, call, "42");
reverts_with_msg!(MatchEnum::RevWLogPrimitive, simulate, "42");
reverts_with_msg!(MatchEnum::RevWLogString, call, "fuel");
reverts_with_msg!(MatchEnum::RevWLogString, simulate, "fuel");
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
call,
"StructDeeplyNestedGeneric"
);
reverts_with_msg!(
MatchEnum::RevWLogCustomGeneric,
simulate,
"StructDeeplyNestedGeneric"
);
}
Ok(())
}
#[tokio::test]
async fn test_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
)
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let error = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_multi_call_contract_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Contract(
name = "ContractLogs",
project = "e2e/sway/logs/contract_logs",
),
Contract(
name = "ContractCaller",
project = "e2e/sway/contracts/lib_contract_caller",
)
),
Deploy(
name = "contract_instance",
contract = "ContractLogs",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "ContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = Contract::load_from(
"./sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let lib_contract_instance = MyContract::new(contract_id.clone(), wallet.clone());
let call_handler_1 = contract_instance.methods().produce_logs_values();
let call_handler_2 = contract_caller_instance
.methods()
.require_from_contract(contract_id)
.with_contracts(&[&lib_contract_instance]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let error = multi_call_handler
.call::<((), ())>()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_script_require_from_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet",
random_salt = false,
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
#[tokio::test]
async fn test_loader_script_require_from_loader_contract() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyContract",
project = "e2e/sway/contracts/lib_contract",
),
Script(
name = "LogScript",
project = "e2e/sway/scripts/require_from_contract"
)
),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let contract_binary = "sway/contracts/lib_contract/out/release/lib_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let mut script_instance = script_instance;
script_instance.convert_into_loader().await?;
let error = script_instance
.main(contract_instance.id())
.with_contracts(&[&contract_instance])
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("require from contract", error);
Ok(())
}
fn assert_assert_eq_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left == right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
fn assert_assert_ne_containing_msg<T: std::fmt::Debug>(left: T, right: T, error: Error) {
let msg = format!(
"assertion failed: `(left != right)`\n left: `\"{left:?}\"`\n right: `\"{right:?}\"`"
);
assert_revert_containing_msg(&msg, error)
}
#[tokio::test]
async fn test_contract_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LogContract",
project = "e2e/sway/contracts/asserts"
)),
Deploy(
name = "contract_instance",
contract = "LogContract",
wallet = "wallet",
random_salt = false,
),
);
macro_rules! reverts_with_msg {
(($($arg: expr,)*), $method:ident, call, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
(($($arg: expr,)*), $method:ident, simulate, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!((32, 64,), assert_primitive, call, "assertion failed");
reverts_with_msg!((32, 64,), assert_primitive, simulate, "assertion failed");
}
macro_rules! reverts_with_assert_eq_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_eq_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, call, "assertion failed");
reverts_with_assert_eq_msg!((32, 64,), assert_eq_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_struct.clone(), test_struct2.clone(),),
assert_eq_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
call,
"assertion failed"
);
reverts_with_assert_eq_msg!(
(test_enum.clone(), test_enum2.clone(),),
assert_eq_enum,
simulate,
"assertion failed"
);
}
macro_rules! reverts_with_assert_ne_msg {
(($($arg: expr,)*), $method:ident, $execution: ident, $msg:expr) => {
let error = contract_instance
.methods()
.$method($($arg,)*)
.call()
.await
.expect_err("should return a revert error");
assert_assert_ne_containing_msg($($arg,)* error);
}
}
{
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, call, "assertion failed");
reverts_with_assert_ne_msg!((32, 32,), assert_ne_primitive, simulate, "assertion failed");
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_struct.clone(), test_struct.clone(),),
assert_ne_struct,
simulate,
"assertion failed"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
call,
"assertion failed"
);
reverts_with_assert_ne_msg!(
(test_enum.clone(), test_enum.clone(),),
assert_ne_enum,
simulate,
"assertion failed"
);
}
Ok(())
}
#[tokio::test]
async fn test_script_asserts_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/scripts/script_asserts"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
macro_rules! reverts_with_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
macro_rules! reverts_with_assert_eq_ne_msg {
($arg:expr, call, $msg:expr) => {
let error = script_instance
.main($arg)
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
($arg:expr, simulate, $msg:expr) => {
let error = script_instance
.main($arg)
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg($msg, error);
};
}
{
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
call,
"assertion failed"
);
reverts_with_msg!(
MatchEnum::AssertPrimitive((32, 64)),
simulate,
"assertion failed"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqPrimitive((32, 64)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
let test_struct2 = TestStruct {
field_1: false,
field_2: 32,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqStruct((test_struct.clone(), test_struct2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
let test_enum2 = TestEnum::VariantTwo;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
call,
"assertion failed: `(left == right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertEqEnum((test_enum.clone(), test_enum2.clone(),)),
simulate,
"assertion failed: `(left == right)`"
);
}
{
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNePrimitive((32, 32)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_struct = TestStruct {
field_1: true,
field_2: 64,
};
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeStruct((test_struct.clone(), test_struct.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
{
let test_enum = TestEnum::VariantOne;
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
call,
"assertion failed: `(left != right)`"
);
reverts_with_assert_eq_ne_msg!(
MatchEnum::AssertNeEnum((test_enum.clone(), test_enum.clone(),)),
simulate,
"assertion failed: `(left != right)`"
);
}
Ok(())
}
#[tokio::test]
async fn contract_token_ops_error_messages() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let contract_id = contract_instance.contract_id();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let address = wallet.address();
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.simulate(Execution::Realistic)
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
let error = contract_methods
.transfer(1_000_000, asset_id, address.into())
.call()
.await
.expect_err("should return a revert error");
assert_revert_containing_msg("failed transfer to address", error);
}
Ok(())
}
#[tokio::test]
async fn test_log_results() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let response = contract_instance
.methods()
.produce_bad_logs()
.call()
.await?;
let log = response.decode_logs();
let expected_err = format!(
"codec: missing log formatter for log_id: `LogId({:?}, \"128\")`, data: `{:?}`. \
Consider adding external contracts using `with_contracts()`",
contract_instance.id().hash,
[0u8; 8]
);
let succeeded = log.filter_succeeded();
let failed = log.filter_failed();
assert_eq!(succeeded, vec!["123".to_string()]);
assert_eq!(failed.first().unwrap().to_string(), expected_err);
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_contract_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: decoding with too low max_tokens fails
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"Should have failed since there are more tokens than what is supported by default.",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "Should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Single call: increasing limits makes the test pass
let response = methods
.i_log_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
{
// Multi call: decoding with too low max_tokens will fail
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call::<((),)>()
.await?;
response.decode_logs_with_type::<[u8; 1000]>().expect_err(
"should have failed since there are more tokens than what is supported by default",
);
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty(), "should have had failed to decode logs since there are more tokens than what is supported by default");
}
{
// Multi call: increasing limits makes the test pass
let response = CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_log_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<((),)>()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty());
}
Ok(())
}
#[tokio::test]
async fn can_configure_decoder_for_script_log_decoding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_needs_custom_decoder_logging"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
{
// Cannot decode the produced log with too low max_tokens
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 100,
..Default::default()
})
.call()
.await?;
response
.decode_logs_with_type::<[u8; 1000]>()
.expect_err("Cannot decode the log with default decoder config");
let logs = response.decode_logs();
assert!(!logs.filter_failed().is_empty())
}
{
// When the token limit is bumped log decoding succeeds
let response = script_instance
.main()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?;
let logs = response.decode_logs_with_type::<[u8; 1000]>()?;
assert_eq!(logs, vec![[0u8; 1000]]);
let logs = response.decode_logs();
assert!(!logs.filter_succeeded().is_empty())
}
Ok(())
}
#[tokio::test]
async fn contract_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/logs/contract_logs"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.produce_string_slice_log().call().await?;
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let response = contract_methods.produce_string_log().call().await?;
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let response = contract_methods.produce_bytes_log().call().await?;
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let response = contract_methods.produce_raw_slice_log().call().await?;
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let response = contract_methods.produce_vec_log().call().await?;
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
#[tokio::test]
async fn script_heap_log() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Script(
name = "LogScript",
project = "e2e/sway/logs/script_heap_logs"
)),
LoadScript(
name = "script_instance",
script = "LogScript",
wallet = "wallet"
)
);
let response = script_instance.main().call().await?;
{
let logs = response.decode_logs_with_type::<AsciiString>()?;
assert_eq!("fuel".to_string(), logs.first().unwrap().to_string());
}
{
let logs = response.decode_logs_with_type::<String>()?;
assert_eq!(vec!["fuel".to_string()], logs);
}
{
let logs = response.decode_logs_with_type::<Bytes>()?;
assert_eq!(vec![Bytes("fuel".as_bytes().to_vec())], logs);
}
{
let logs = response.decode_logs_with_type::<RawSlice>()?;
assert_eq!(vec![RawSlice("fuel".as_bytes().to_vec())], logs);
}
{
let v = [1u16, 2, 3].to_vec();
let some_enum = EnumWithGeneric::VariantOne(v);
let other_enum = EnumWithGeneric::VariantTwo;
let v1 = vec![some_enum.clone(), other_enum, some_enum];
let expected_vec = vec![vec![v1.clone(), v1]];
let logs = response.decode_logs_with_type::<Vec<Vec<Vec<EnumWithGeneric<Vec<u16>>>>>>()?;
assert_eq!(vec![expected_vec], logs);
}
Ok(())
}
Convert to Identity
Convert an Address to an Identity:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a ContractId to an Identity:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to AssetId
Convert a [u8; 32] array to an AssetId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a hex string to an AssetId:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to Bech32
Convert a [u8; 32] array to a Bech32 address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert Bytes32 to a Bech32 address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert a string to a Bech32 address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert an Address to a Bech32 address:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to str
Convert a ContractId to a str:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert an Address to a str:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert an AssetId to a str:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert Bytes32 to a str:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to Bits256
Convert a hex string to Bits256:
use fuel_types::AssetId;
use fuels_macros::{Parameterize, Tokenizable, TryFrom};
use crate::types::errors::Result;
// A simple wrapper around [u8; 32] representing the `b256` type. Exists
// mainly so that we may differentiate `Parameterize` and `Tokenizable`
// implementations from what otherwise is just an array of 32 u8's.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Bits256(pub [u8; 32]);
impl Bits256 {
/// Returns `Self` with zeroes inside.
pub fn zeroed() -> Self {
Self([0; 32])
}
/// Create a new `Bits256` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let mut bytes = [0u8; 32];
hex::decode_to_slice(hex, &mut bytes as &mut [u8])?;
Ok(Bits256(bytes))
}
}
impl From<AssetId> for Bits256 {
fn from(value: AssetId) -> Self {
Self(value.into())
}
}
// A simple wrapper around [Bits256; 2] representing the `B512` type.
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: b512
pub struct B512 {
pub bytes: [Bits256; 2],
}
// ANCHOR_END: b512
impl From<(Bits256, Bits256)> for B512 {
fn from(bits_tuple: (Bits256, Bits256)) -> Self {
B512 {
bytes: [bits_tuple.0, bits_tuple.1],
}
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Parameterize, Tokenizable, TryFrom)]
#[FuelsCorePath = "crate"]
#[FuelsTypesPath = "crate::types"]
// ANCHOR: evm_address
pub struct EvmAddress {
// An evm address is only 20 bytes, the first 12 bytes should be set to 0
value: Bits256,
}
// ANCHOR_END: evm_address
impl EvmAddress {
fn new(b256: Bits256) -> Self {
Self {
value: Bits256(Self::clear_12_bytes(b256.0)),
}
}
pub fn value(&self) -> Bits256 {
self.value
}
// sets the leftmost 12 bytes to zero
fn clear_12_bytes(bytes: [u8; 32]) -> [u8; 32] {
let mut bytes = bytes;
bytes[..12].copy_from_slice(&[0u8; 12]);
bytes
}
}
impl From<Bits256> for EvmAddress {
fn from(b256: Bits256) -> Self {
EvmAddress::new(b256)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
traits::{Parameterize, Tokenizable},
types::{param_types::ParamType, Token},
};
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
assert_eq!(bits256.0, [1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_str_to_bits256
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bits256 = Bits256::from_hex_str(hex_str)?;
// ANCHOR_END: hex_str_to_bits256
assert_eq!(bits256.0, [1u8; 32]);
// ANCHOR_END: from_hex_str
Ok(())
}
#[test]
fn test_param_type_evm_addr() {
assert_eq!(
EvmAddress::param_type(),
ParamType::Struct {
name: "EvmAddress".to_string(),
fields: vec![("value".to_string(), ParamType::B256)],
generics: vec![]
}
);
}
#[test]
fn evm_address_clears_first_12_bytes() -> Result<()> {
let data = [1u8; 32];
let address = EvmAddress::new(Bits256(data));
let expected_data = Bits256([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
]);
assert_eq!(address.value(), expected_data);
Ok(())
}
#[test]
fn test_into_token_evm_addr() {
let bits = [1u8; 32];
let evm_address = EvmAddress::from(Bits256(bits));
let token = evm_address.into_token();
let expected_data = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1,
];
assert_eq!(token, Token::Struct(vec![Token::B256(expected_data)]));
}
}
Convert a ContractId to Bits256:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert an Address to Bits256:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert an AssetId to Bits256:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Convert to Bytes
Convert a string to Bytes:
use crate::types::errors::Result;
#[derive(Debug, PartialEq, Clone, Eq)]
pub struct Bytes(pub Vec<u8>);
impl Bytes {
/// Create a new `Bytes` from a string representation of a hex.
/// Accepts both `0x` prefixed and non-prefixed hex strings.
pub fn from_hex_str(hex: &str) -> Result<Self> {
let hex = if let Some(stripped_hex) = hex.strip_prefix("0x") {
stripped_hex
} else {
hex
};
let bytes = hex::decode(hex)?;
Ok(Bytes(bytes))
}
}
impl From<Bytes> for Vec<u8> {
fn from(bytes: Bytes) -> Vec<u8> {
bytes.0
}
}
impl PartialEq<Vec<u8>> for Bytes {
fn eq(&self, other: &Vec<u8>) -> bool {
self.0 == *other
}
}
impl PartialEq<Bytes> for Vec<u8> {
fn eq(&self, other: &Bytes) -> bool {
*self == other.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_hex_str_b256() -> Result<()> {
// ANCHOR: bytes_from_hex_str
let hex_str = "0101010101010101010101010101010101010101010101010101010101010101";
let bytes = Bytes::from_hex_str(hex_str)?;
assert_eq!(bytes.0, vec![1u8; 32]);
// With the `0x0` prefix
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0101010101010101010101010101010101010101010101010101010101010101";
let bytes = Bytes::from_hex_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!(bytes.0, vec![1u8; 32]);
// ANCHOR_END: bytes_from_hex_str
Ok(())
}
}
Convert to B512
Convert two hex strings to B512:
use std::str::FromStr;
use fuels::{
prelude::*,
types::{Bits256, EvmAddress, Identity, SizedAsciiString, B512, U256},
};
pub fn null_contract_id() -> Bech32ContractId {
// a bech32 contract address that decodes to [0u8;32]
Bech32ContractId::from_str("fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2")
.expect("is valid")
}
#[tokio::test]
async fn test_methods_typeless_argument() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/empty_arguments"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.method_with_empty_argument()
.call()
.await?;
assert_eq!(response.value, 63);
Ok(())
}
#[tokio::test]
async fn call_with_empty_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/types/contracts/call_empty_return"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let _response = contract_instance.methods().store_value(42).call().await?;
Ok(())
}
#[tokio::test]
async fn call_with_structs() -> Result<()> {
// Generates the bindings from the an ABI definition inline.
// The generated bindings can be accessed through `MyContract`.
// ANCHOR: struct_generation
abigen!(Contract(name="MyContract",
abi="e2e/sway/types/contracts/complex_types_contract/out/release/complex_types_contract-abi.json"));
// Here we can use `CounterConfig`, a struct originally
// defined in the contract.
let counter_config = CounterConfig {
dummy: true,
initial_value: 42,
};
// ANCHOR_END: struct_generation
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"sway/types/contracts/complex_types_contract/out/release/complex_types_contract.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet).methods();
let response = contract_methods
.initialize_counter(counter_config)
.call()
.await?;
assert_eq!(42, response.value);
let response = contract_methods.increment_counter(10).call().await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn abigen_different_structs_same_arg_name() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/two_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let param_one = StructOne { foo: 42 };
let param_two = StructTwo { bar: 42 };
let contract_methods = contract_instance.methods();
let res_one = contract_methods.something(param_one).call().await?;
assert_eq!(res_one.value, 43);
let res_two = contract_methods.something_else(param_two).call().await?;
assert_eq!(res_two.value, 41);
Ok(())
}
#[tokio::test]
async fn nested_structs() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/nested_structs"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = AllStruct {
some_struct: SomeStruct {
field: 12345,
field_2: true,
},
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_struct().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_struct_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the argument correctly. Investigate!"
);
let memory_address = MemoryAddress {
contract_id: ContractId::zeroed(),
function_selector: 10,
function_data: 0,
};
let call_data = CallData {
memory_address,
num_coins_to_forward: 10,
asset_id_of_coins_to_forward: ContractId::zeroed(),
amount_of_gas_to_forward: 5,
};
let actual = contract_methods
.nested_struct_with_reserved_keyword_substring(call_data.clone())
.call()
.await?
.value;
assert_eq!(actual, call_data);
Ok(())
}
#[tokio::test]
async fn calls_with_empty_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/complex_types_contract"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.get_empty_struct().call().await?;
assert_eq!(response.value, EmptyStruct {});
}
{
let response = contract_methods
.input_empty_struct(EmptyStruct {})
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_struct_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let cocktail_in_bytes: Vec<u8> = vec![
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3,
];
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(2),
glass: 3,
};
// as slice
let actual: Cocktail = cocktail_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Cocktail = (&cocktail_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Cocktail = cocktail_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn test_tuples() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/tuples"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let response = contract_methods.returns_tuple((1, 2)).call().await?;
assert_eq!(response.value, (1, 2));
}
{
// Tuple with struct.
let my_struct_tuple = (
42,
Person {
name: "Jane".try_into()?,
},
);
let response = contract_methods
.returns_struct_in_tuple(my_struct_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_struct_tuple);
}
{
// Tuple with enum.
let my_enum_tuple: (u64, State) = (42, State::A);
let response = contract_methods
.returns_enum_in_tuple(my_enum_tuple.clone())
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// Tuple with single element
let my_enum_tuple = (123u64,);
let response = contract_methods
.single_element_tuple(my_enum_tuple)
.call()
.await?;
assert_eq!(response.value, my_enum_tuple);
}
{
// tuple with b256
let id = *ContractId::zeroed();
let my_b256_u8_tuple = (Bits256(id), 10);
let response = contract_methods
.tuple_with_b256(my_b256_u8_tuple)
.call()
.await?;
assert_eq!(response.value, my_b256_u8_tuple);
}
Ok(())
}
#[tokio::test]
async fn test_evm_address() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/evm_address"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
{
// ANCHOR: evm_address_arg
let b256 = Bits256::from_hex_str(
"0x1616060606060606060606060606060606060606060606060606060606060606",
)?;
let evm_address = EvmAddress::from(b256);
let call_handler = contract_instance
.methods()
.evm_address_as_input(evm_address);
// ANCHOR_END: evm_address_arg
assert!(call_handler.call().await?.value);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_literal()
.call()
.await?
.value,
expected_evm_address
);
}
{
let b256 = Bits256::from_hex_str(
"0x0606060606060606060606060606060606060606060606060606060606060606",
)?;
let expected_evm_address = EvmAddress::from(b256);
assert_eq!(
contract_instance
.methods()
.evm_address_from_argument(b256)
.call()
.await?
.value,
expected_evm_address
);
}
Ok(())
}
#[tokio::test]
async fn test_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
contract_instance
.methods()
.get_array([42; 2])
.call()
.await?
.value,
[42; 2]
);
Ok(())
}
#[tokio::test]
async fn test_arrays_with_custom_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let persons = [
Person {
name: "John".try_into()?,
},
Person {
name: "Jane".try_into()?,
},
];
let contract_methods = contract_instance.methods();
let response = contract_methods.array_of_structs(persons).call().await?;
assert_eq!("John", response.value[0].name);
assert_eq!("Jane", response.value[1].name);
let states = [State::A, State::B];
let response = contract_methods
.array_of_enums(states.clone())
.call()
.await?;
assert_eq!(states[0], response.value[0]);
assert_eq!(states[1], response.value[1]);
Ok(())
}
#[tokio::test]
async fn str_in_array() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/str_in_array"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let input = ["foo", "bar", "baz"].map(|str| str.try_into().unwrap());
let contract_methods = contract_instance.methods();
let response = contract_methods
.take_array_string_shuffle(input.clone())
.call()
.await?;
assert_eq!(response.value, ["baz", "foo", "bar"]);
let response = contract_methods
.take_array_string_return_single(input.clone())
.call()
.await?;
assert_eq!(response.value, ["foo"]);
let response = contract_methods
.take_array_string_return_single_element(input)
.call()
.await?;
assert_eq!(response.value, "bar");
Ok(())
}
#[tokio::test]
async fn test_enum_inside_struct() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_inside_struct"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = Cocktail {
the_thing_you_mix_in: Shaker::Mojito(11),
glass: 333,
};
let contract_methods = contract_instance.methods();
let response = contract_methods
.return_enum_inside_struct(11)
.call()
.await?;
assert_eq!(response.value, expected);
let enum_inside_struct = Cocktail {
the_thing_you_mix_in: Shaker::Cosmopolitan(444),
glass: 555,
};
let response = contract_methods
.take_enum_inside_struct(enum_inside_struct)
.call()
.await?;
assert_eq!(response.value, 555);
Ok(())
}
#[tokio::test]
async fn native_types_support() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/native_types"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let user = User {
weight: 10,
address: Address::zeroed(),
};
let contract_methods = contract_instance.methods();
let response = contract_methods.wrapped_address(user).call().await?;
assert_eq!(response.value.address, Address::zeroed());
let response = contract_methods
.unwrapped_address(Address::zeroed())
.call()
.await?;
assert_eq!(
response.value,
Address::from_str("0x0000000000000000000000000000000000000000000000000000000000000000")?
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_variable_width_variants() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of enum encoding width, then we'll
// probably end up mangling arg_2 and onward which will fail this test.
let expected = BigBundle {
arg_1: EnumThatHasABigAndSmallVariant::Small(12345),
arg_2: 6666,
arg_3: 7777,
arg_4: 8888,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_big_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_big_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_coding_w_unit_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_encoding"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// If we had a regression on the issue of unit enum encoding width, then
// we'll end up mangling arg_2
let expected = UnitBundle {
arg_1: UnitEnum::var2,
arg_2: u64::MAX,
};
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_unit_bundle().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_bundle_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the bundle correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn enum_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/enum_as_input"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected = MaxedOutVariantsEnum::Variant255(11);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_max_variant().call().await?.value;
assert_eq!(expected, actual);
let expected = StandardEnum::Two(12345);
let contract_methods = contract_instance.methods();
let actual = contract_methods.get_standard_enum().call().await?.value;
assert_eq!(expected, actual);
let fuelvm_judgement = contract_methods
.check_standard_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the standard enum correctly. Investigate!"
);
let expected = UnitEnum::Two;
let actual = contract_methods.get_unit_enum().call().await?.value;
assert_eq!(actual, expected);
let fuelvm_judgement = contract_methods
.check_unit_enum_integrity(expected)
.call()
.await?
.value;
assert!(
fuelvm_judgement,
"The FuelVM deems that we've not encoded the unit enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn can_use_try_into_to_construct_enum_from_bytes() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/enum_inside_struct/out/release\
/enum_inside_struct-abi.json"
));
let shaker_in_bytes: Vec<u8> = vec![0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let expected = Shaker::Mojito(2);
// as slice
let actual: Shaker = shaker_in_bytes[..].try_into()?;
assert_eq!(actual, expected);
// as ref
let actual: Shaker = (&shaker_in_bytes).try_into()?;
assert_eq!(actual, expected);
// as value
let actual: Shaker = shaker_in_bytes.try_into()?;
assert_eq!(actual, expected);
Ok(())
}
#[tokio::test]
async fn type_inside_enum() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/type_inside_enum"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
// String inside enum
let enum_string = SomeEnum::SomeStr("asdf".try_into()?);
let contract_methods = contract_instance.methods();
let response = contract_methods
.str_inside_enum(enum_string.clone())
.call()
.await?;
assert_eq!(response.value, enum_string);
// Array inside enum
let enum_array = SomeEnum::SomeArr([1, 2, 3, 4]);
let response = contract_methods
.arr_inside_enum(enum_array.clone())
.call()
.await?;
assert_eq!(response.value, enum_array);
// Struct inside enum
let response = contract_methods
.return_struct_inside_enum(11)
.call()
.await?;
let expected = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 11 });
assert_eq!(response.value, expected);
let struct_inside_enum = Shaker::Cosmopolitan(Recipe { ice: 22, sugar: 66 });
let response = contract_methods
.take_struct_inside_enum(struct_inside_enum)
.call()
.await?;
assert_eq!(response.value, 8888);
// Enum inside enum
let expected_enum = EnumLevel3::El2(EnumLevel2::El1(EnumLevel1::Num(42)));
let response = contract_methods.get_nested_enum().call().await?;
assert_eq!(response.value, expected_enum);
let response = contract_methods
.check_nested_enum_integrity(expected_enum)
.call()
.await?;
assert!(
response.value,
"The FuelVM deems that we've not encoded the nested enum correctly. Investigate!"
);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_some_address = Some(expected_address);
let response = contract_methods.get_some_address().call().await?;
assert_eq!(response.value, expected_some_address);
let expected_some_u64 = Some(10);
let response = contract_methods.get_some_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_some_struct().call().await?;
assert_eq!(response.value, Some(s.clone()));
let response = contract_methods.get_some_enum().call().await?;
assert_eq!(response.value, Some(e.clone()));
let response = contract_methods.get_some_tuple().call().await?;
assert_eq!(response.value, Some((s.clone(), e.clone())));
let expected_none = None;
let response = contract_methods.get_none().call().await?;
assert_eq!(response.value, expected_none);
Ok(())
}
#[tokio::test]
async fn test_rust_option_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/options"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_u64 = Some(36);
let response = contract_methods
.input_primitive(expected_u64)
.call()
.await?;
assert!(response.value);
let expected_struct = Some(s);
let response = contract_methods
.input_struct(expected_struct)
.call()
.await?;
assert!(response.value);
let expected_enum = Some(e);
let response = contract_methods.input_enum(expected_enum).call().await?;
assert!(response.value);
let expected_none = None;
let response = contract_methods.input_none(expected_none).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
option: Some(expected_address),
};
let e = TestEnum::EnumOption(Some(expected_address));
let expected_ok_address = Ok(expected_address);
let response = contract_methods.get_ok_address().call().await?;
assert_eq!(response.value, expected_ok_address);
let expected_some_u64 = Ok(10);
let response = contract_methods.get_ok_u64().call().await?;
assert_eq!(response.value, expected_some_u64);
let response = contract_methods.get_ok_struct().call().await?;
assert_eq!(response.value, Ok(s.clone()));
let response = contract_methods.get_ok_enum().call().await?;
assert_eq!(response.value, Ok(e.clone()));
let response = contract_methods.get_ok_tuple().call().await?;
assert_eq!(response.value, Ok((s, e)));
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.get_error().call().await?;
assert_eq!(response.value, expected_error);
Ok(())
}
#[tokio::test]
async fn test_rust_result_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/results"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_ok_address = Ok(expected_address);
let response = contract_methods
.input_ok(expected_ok_address)
.call()
.await?;
assert!(response.value);
let expected_error = Err(TestError::NoAddress("error".try_into().unwrap()));
let response = contract_methods.input_error(expected_error).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_decoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods.get_identity_address().call().await?;
assert_eq!(response.value, Identity::Address(expected_address));
let response = contract_methods.get_identity_contract_id().call().await?;
assert_eq!(response.value, Identity::ContractId(expected_contract_id));
let response = contract_methods.get_struct_with_identity().call().await?;
assert_eq!(response.value, s.clone());
let response = contract_methods.get_enum_with_identity().call().await?;
assert_eq!(response.value, e.clone());
let response = contract_methods.get_identity_tuple().call().await?;
assert_eq!(response.value, (s, e));
Ok(())
}
#[tokio::test]
async fn test_identity_can_be_encoded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let expected_contract_id =
ContractId::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
let s = TestStruct {
identity: Identity::Address(expected_address),
};
let e = TestEnum::EnumIdentity(Identity::ContractId(expected_contract_id));
let response = contract_methods
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
let response = contract_methods
.input_struct_with_identity(s)
.call()
.await?;
assert!(response.value);
let response = contract_methods.input_enum_with_identity(e).call().await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_identity_with_two_contracts() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/identity"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance2",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let expected_address =
Address::from_str("0xd58573593432a30a800f97ad32f877425c223a9e427ab557aab5d5bb89156db0")?;
{
let response = contract_instance
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
{
let response = contract_instance2
.methods()
.input_identity(Identity::Address(expected_address))
.call()
.await?;
assert!(response.value);
}
Ok(())
}
#[tokio::test]
async fn generics_test() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/generics"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: generic
// simple struct with a single generic param
let arg1 = SimpleGeneric {
single_generic_param: 123u64,
};
let result = contract_methods
.struct_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
// ANCHOR_END: generic
}
{
// struct that delegates the generic param internally
let arg1 = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "abc".try_into()?,
},
};
let result = contract_methods
.struct_delegating_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in an array
let arg1 = StructWArrayGeneric { a: [1u32, 2u32] };
let result = contract_methods
.struct_w_generic_in_array(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has a generic struct in an array
let inner = [
StructWTwoGenerics {
a: Bits256([1u8; 32]),
b: 1,
},
StructWTwoGenerics {
a: Bits256([2u8; 32]),
b: 2,
},
StructWTwoGenerics {
a: Bits256([3u8; 32]),
b: 3,
},
];
let arg1 = StructWArrWGenericStruct { a: inner };
let result = contract_methods
.array_with_generic_struct(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// struct that has the generic in a tuple
let arg1 = StructWTupleGeneric { a: (1, 2) };
let result = contract_methods
.struct_w_generic_in_tuple(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
// enum with generic in variant
let arg1 = EnumWGeneric::B(10);
let result = contract_methods
.enum_w_generic(arg1.clone())
.call()
.await?
.value;
assert_eq!(result, arg1);
}
{
contract_methods
.unused_generic_args(StructUnusedGeneric::new(15), EnumUnusedGeneric::One(15))
.call()
.await?;
let (the_struct, the_enum) = contract_methods
.used_and_unused_generic_args(
StructUsedAndUnusedGenericParams::new(10u8),
EnumUsedAndUnusedGenericParams::Two(11u8),
)
.call()
.await?
.value;
assert_eq!(the_struct.field, 12u8);
if let EnumUsedAndUnusedGenericParams::Two(val) = the_enum {
assert_eq!(val, 13)
} else {
panic!("Expected the variant EnumUsedAndUnusedGenericParams::Two");
}
}
{
// complex case
let pass_through = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: "ab".try_into()?,
},
};
let w_arr_generic = StructWArrayGeneric {
a: [pass_through.clone(), pass_through],
};
let arg1 = MegaExample {
a: ([Bits256([0; 32]), Bits256([0; 32])], "ab".try_into()?),
b: vec![(
[EnumWGeneric::B(StructWTupleGeneric {
a: (w_arr_generic.clone(), w_arr_generic),
})],
10u32,
)],
};
contract_methods.complex_test(arg1.clone()).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_vectors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/vectors"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let methods = contract_instance.methods();
{
// vec of u32s
let arg = vec![0, 1, 2];
methods.u32_vec(arg).call().await?;
}
{
// vec of vecs of u32s
let arg = vec![vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_vec(arg.clone()).call().await?;
}
{
// vec of structs
// ANCHOR: passing_in_vec
let arg = vec![SomeStruct { a: 0 }, SomeStruct { a: 1 }];
methods.struct_in_vec(arg.clone()).call().await?;
// ANCHOR_END: passing_in_vec
}
{
// vec in struct
let arg = SomeStruct { a: vec![0, 1, 2] };
methods.vec_in_struct(arg.clone()).call().await?;
}
{
// array in vec
let arg = vec![[0u64, 1u64], [0u64, 1u64]];
methods.array_in_vec(arg.clone()).call().await?;
}
{
// vec in array
let arg = [vec![0, 1, 2], vec![0, 1, 2]];
methods.vec_in_array(arg.clone()).call().await?;
}
{
// vec in enum
let arg = SomeEnum::a(vec![0, 1, 2]);
methods.vec_in_enum(arg.clone()).call().await?;
}
{
// enum in vec
let arg = vec![SomeEnum::a(0), SomeEnum::a(1)];
methods.enum_in_vec(arg.clone()).call().await?;
}
{
// tuple in vec
let arg = vec![(0, 0), (1, 1)];
methods.tuple_in_vec(arg.clone()).call().await?;
}
{
// vec in tuple
let arg = (vec![0, 1, 2], vec![0, 1, 2]);
methods.vec_in_tuple(arg.clone()).call().await?;
}
{
// vec in a vec in a struct in a vec
let arg = vec![
SomeStruct {
a: vec![vec![0, 1, 2], vec![3, 4, 5]],
},
SomeStruct {
a: vec![vec![6, 7, 8], vec![9, 10, 11]],
},
];
methods
.vec_in_a_vec_in_a_struct_in_a_vec(arg.clone())
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_b256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(
Bits256([2; 32]),
contract_instance
.methods()
.b256_as_output()
.call()
.await?
.value
);
{
// ANCHOR: 256_arg
let b256 = Bits256([1; 32]);
let call_handler = contract_instance.methods().b256_as_input(b256);
// ANCHOR_END: 256_arg
assert!(call_handler.call().await?.value);
}
Ok(())
}
#[tokio::test]
async fn test_b512() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/b512"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: b512_example
let hi_bits = Bits256::from_hex_str(
"0xbd0c9b8792876713afa8bff383eebf31c43437823ed761cc3600d0016de5110c",
)?;
let lo_bits = Bits256::from_hex_str(
"0x44ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits));
// ANCHOR_END: b512_example
assert_eq!(b512, contract_methods.b512_as_output().call().await?.value);
{
let lo_bits2 = Bits256::from_hex_str(
"0x54ac566bd156b4fc71a4a4cb2655d3dd360c695edb17dc3b64d611e122fea23d",
)?;
let b512 = B512::from((hi_bits, lo_bits2));
assert!(contract_methods.b512_as_input(b512).call().await?.value);
}
Ok(())
}
fn u128_from(parts: (u64, u64)) -> u128 {
let bytes: [u8; 16] = [parts.0.to_be_bytes(), parts.1.to_be_bytes()]
.concat()
.try_into()
.unwrap();
u128::from_be_bytes(bytes)
}
#[tokio::test]
async fn test_u128() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u128"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u128_from((1, 2));
let actual = contract_methods.u128_sum_and_ret(arg).call().await?.value;
let expected = arg + u128_from((3, 4));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u128_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u128_from((4, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u128_from((3, 3)));
contract_methods.u128_in_enum_input(input).call().await?;
}
Ok(())
}
fn u256_from(parts: (u64, u64, u64, u64)) -> U256 {
let bytes: [u8; 32] = [
parts.0.to_be_bytes(),
parts.1.to_be_bytes(),
parts.2.to_be_bytes(),
parts.3.to_be_bytes(),
]
.concat()
.try_into()
.unwrap();
U256::from(bytes)
}
#[tokio::test]
async fn test_u256() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TypesContract",
project = "e2e/sway/types/contracts/u256"
)),
Deploy(
name = "contract_instance",
contract = "TypesContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let arg = u256_from((1, 2, 3, 4));
let actual = contract_methods.u256_sum_and_ret(arg).call().await?.value;
let expected = arg + u256_from((3, 4, 5, 6));
assert_eq!(expected, actual);
}
{
let actual = contract_methods.u256_in_enum_output().call().await?.value;
let expected = SomeEnum::B(u256_from((1, 2, 3, 4)));
assert_eq!(expected, actual);
}
{
let input = SomeEnum::B(u256_from((2, 3, 4, 5)));
contract_methods.u256_in_enum_input(input).call().await?;
}
Ok(())
}
#[tokio::test]
async fn test_base_type_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
// ANCHOR: returning_vec
let response = contract_methods.u8_in_vec(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
// ANCHOR_END: returning_vec
let response = contract_methods.u16_in_vec(11).call().await?;
assert_eq!(response.value, (0..11).collect::<Vec<_>>());
let response = contract_methods.u32_in_vec(12).call().await?;
assert_eq!(response.value, (0..12).collect::<Vec<_>>());
let response = contract_methods.u64_in_vec(13).call().await?;
assert_eq!(response.value, (0..13).collect::<Vec<_>>());
let response = contract_methods.bool_in_vec().call().await?;
assert_eq!(response.value, [true, false, true, false].to_vec());
let response = contract_methods.b256_in_vec(13).call().await?;
assert_eq!(response.value, vec![Bits256([2; 32]); 13]);
Ok(())
}
#[tokio::test]
async fn test_composite_types_in_vec_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let expected: Vec<[u64; 4]> = vec![[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3], [4, 4, 4, 4]];
let response = contract_methods.array_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Pasta> = vec![
Pasta::Tortelini(Bimbam {
bim: 1111,
bam: 2222_u32,
}),
Pasta::Rigatoni(1987),
Pasta::Spaghetti(true),
];
let response = contract_methods.enum_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<Bimbam> = vec![
Bimbam {
bim: 1111,
bam: 2222_u32,
},
Bimbam {
bim: 3333,
bam: 4444_u32,
},
Bimbam {
bim: 5555,
bam: 6666_u32,
},
];
let response = contract_methods.struct_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<(u64, u32)> = vec![(1111, 2222_u32), (3333, 4444_u32), (5555, 6666_u32)];
let response = contract_methods.tuple_in_vec().call().await?.value;
assert_eq!(response, expected);
}
{
let expected: Vec<SizedAsciiString<4>> =
vec!["hell".try_into()?, "ello".try_into()?, "lloh".try_into()?];
let response = contract_methods.str_in_vec().call().await?.value;
assert_eq!(response, expected);
}
Ok(())
}
#[tokio::test]
async fn test_bytes_output() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesOutputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods.return_bytes(10).call().await?;
assert_eq!(response.value, (0..10).collect::<Vec<_>>());
Ok(())
}
#[tokio::test]
async fn test_bytes_as_input() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BytesInputContract",
project = "e2e/sway/types/contracts/bytes"
)),
Deploy(
name = "contract_instance",
contract = "BytesInputContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
// ANCHOR: bytes_arg
let bytes = Bytes(vec![40, 41, 42]);
contract_methods.accept_bytes(bytes).call().await?;
// ANCHOR_END: bytes_arg
}
{
let bytes = Bytes(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![bytes.clone(), bytes.clone()],
inner_enum: SomeEnum::Second(bytes),
};
contract_methods.accept_nested_bytes(wrapper).call().await?;
}
Ok(())
}
#[tokio::test]
async fn contract_raw_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RawSliceContract",
project = "e2e/sway/types/contracts/raw_slice"
)),
Deploy(
name = "contract_instance",
contract = "RawSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
for length in 0u8..=10 {
let response = contract_methods.return_raw_slice(length).call().await?;
assert_eq!(response.value, (0u8..length).collect::<Vec<u8>>());
}
}
{
contract_methods
.accept_raw_slice(RawSlice(vec![40, 41, 42]))
.call()
.await?;
}
{
let raw_slice = RawSlice(vec![40, 41, 42]);
let wrapper = Wrapper {
inner: vec![raw_slice.clone(), raw_slice.clone()],
inner_enum: SomeEnum::Second(raw_slice),
};
contract_methods
.accept_nested_raw_slice(wrapper)
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn contract_string_slice() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StringSliceContract",
project = "e2e/sway/types/contracts/string_slice"
)),
Deploy(
name = "contract_instance",
contract = "StringSliceContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.handles_str("contract-input".try_into()?)
.call()
.await?;
assert_eq!(response.value, "contract-return");
Ok(())
}
#[tokio::test]
async fn contract_std_lib_string() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "StdLibString",
project = "e2e/sway/types/contracts/std_lib_string"
)),
Deploy(
name = "contract_instance",
contract = "StdLibString",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.return_dynamic_string().call().await?.value;
assert_eq!(resp, "Hello World");
}
{
let _resp = contract_methods
.accepts_dynamic_string(String::from("Hello World"))
.call()
.await?;
}
{
// confirm encoding/decoding a string wasn't faulty and led to too high gas consumption
let _resp = contract_methods
.echoes_dynamic_string(String::from("Hello Fuel"))
.with_tx_policies(TxPolicies::default().with_script_gas_limit(3600))
.call()
.await?;
}
Ok(())
}
#[tokio::test]
async fn test_heap_type_in_enums() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_type_in_enums"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
{
let resp = contract_methods.returns_bytes_result(true).call().await?;
let expected = Ok(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_result(false).call().await?;
let expected = Err(TestError::Something([255u8, 255u8, 255u8, 255u8, 255u8]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(true).call().await?;
let expected = Ok(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_result(false).call().await?;
let expected = Err(TestError::Else(7777));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(true).call().await?;
let expected = Ok("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_str_result(true).call().await?;
let expected = Ok("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_result(false).call().await?;
let expected = Err(TestError::Else(3333));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(true).call().await?;
let expected = Some(Bytes(vec![1, 1, 1, 1]));
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_bytes_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_vec_option(true).call().await?;
let expected = Some(vec![2, 2, 2, 2, 2]);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_vec_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_string_option(true).call().await?;
let expected = Some("Hello World".to_string());
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
{
let resp = contract_methods.returns_str_option(true).call().await?;
let expected = Some("Hello World".try_into()?);
assert_eq!(resp.value, expected);
}
{
let resp = contract_methods.returns_string_option(false).call().await?;
assert!(resp.value.is_none());
}
Ok(())
}
#[tokio::test]
async fn nested_heap_types() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "HeapTypeInEnum",
project = "e2e/sway/types/contracts/heap_types"
)),
Deploy(
name = "contract_instance",
contract = "HeapTypeInEnum",
wallet = "wallet",
random_salt = false,
),
);
let arr = [2u8, 4, 8];
let struct_generics = StructGenerics {
one: Bytes(arr.to_vec()),
two: String::from("fuel"),
three: RawSlice(arr.to_vec()),
};
let enum_vec = [struct_generics.clone(), struct_generics].to_vec();
let expected = EnumGeneric::One(enum_vec);
let result = contract_instance
.methods()
.nested_heap_types()
.call()
.await?;
assert_eq!(result.value, expected);
Ok(())
}
Convert to EvmAddress
Convert a Bits256 address to an EvmAddress:
#[cfg(test)]
mod tests {
use std::str::FromStr;
use fuels::{
prelude::Result,
types::{Bits256, EvmAddress, Identity},
};
#[tokio::test]
async fn bytes32() -> Result<()> {
// ANCHOR: bytes32
use std::str::FromStr;
use fuels::types::Bytes32;
// Zeroed Bytes32
let b256 = Bytes32::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *b256);
// From a `[u8; 32]`.
// ANCHOR: array_to_bytes32
let my_slice = [1u8; 32];
let b256 = Bytes32::new(my_slice);
// ANCHOR_END: array_to_bytes32
assert_eq!([1u8; 32], *b256);
// From a hex string.
// ANCHOR: hex_string_to_bytes32
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let b256 = Bytes32::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_bytes32
assert_eq!([0u8; 32], *b256);
// ANCHOR_END: bytes32
// ANCHOR: bytes32_format
let b256_string = b256.to_string();
let b256_hex_string = format!("{b256:#x}");
// ANCHOR_END: bytes32_format
assert_eq!(hex_str[2..], b256_string);
assert_eq!(hex_str, b256_hex_string);
// ANCHOR: bytes32_to_str
let _str_from_bytes32: &str = b256.to_string().as_str();
// ANCHOR_END: bytes32_to_str
Ok(())
}
#[tokio::test]
async fn address() -> Result<()> {
// ANCHOR: address
use std::str::FromStr;
use fuels::types::Address;
// Zeroed Bytes32
let address = Address::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *address);
// From a `[u8; 32]`.
// ANCHOR: array_to_address
let my_slice = [1u8; 32];
let address = Address::new(my_slice);
// ANCHOR_END: array_to_address
assert_eq!([1u8; 32], *address);
// From a string.
// ANCHOR: hex_string_to_address
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let address = Address::from_str(hex_str)?;
// ANCHOR_END: hex_string_to_address
assert_eq!([0u8; 32], *address);
// ANCHOR_END: address
// ANCHOR: address_to_identity
let _identity_from_address = Identity::Address(address);
// ANCHOR_END: address_to_identity
// ANCHOR: address_to_str
let _str_from_address: &str = address.to_string().as_str();
// ANCHOR_END: address_to_str
// ANCHOR: address_to_bits256
let bits_256 = Bits256(address.into());
// ANCHOR_END: address_to_bits256
// ANCHOR: b256_to_evm_address
let _evm_address = EvmAddress::from(bits_256);
// ANCHOR_END: b256_to_evm_address
Ok(())
}
#[tokio::test]
async fn bech32() -> Result<()> {
// ANCHOR: bech32
use fuels::types::{bech32::Bech32Address, Address, Bytes32};
// New from HRP string and a hash
// ANCHOR: array_to_bech32
let hrp = "fuel";
let my_slice = [1u8; 32];
let _bech32_address = Bech32Address::new(hrp, my_slice);
// ANCHOR_END: array_to_bech32
// Note that you can also pass a hash stored as Bytes32 to new:
// ANCHOR: bytes32_to_bech32
let my_hash = Bytes32::new([1u8; 32]);
let _bech32_address = Bech32Address::new(hrp, my_hash);
// ANCHOR_END: bytes32_to_bech32
// From a string.
// ANCHOR: str_to_bech32
let address = "fuel1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqsx2mt2";
let bech32_address = Bech32Address::from_str(address)?;
// ANCHOR_END: str_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// From Address
// ANCHOR: address_to_bech32
let plain_address = Address::new([0u8; 32]);
let bech32_address = Bech32Address::from(plain_address);
// ANCHOR_END: address_to_bech32
assert_eq!([0u8; 32], *bech32_address.hash());
// Convert to Address
// ANCHOR: bech32_to_address
let _plain_address: Address = bech32_address.into();
// ANCHOR_END: bech32_to_address
// ANCHOR_END: bech32
Ok(())
}
#[tokio::test]
async fn asset_id() -> Result<()> {
// ANCHOR: asset_id
use std::str::FromStr;
use fuels::types::AssetId;
// Zeroed Bytes32
let asset_id = AssetId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *asset_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_asset_id
let my_slice = [1u8; 32];
let asset_id = AssetId::new(my_slice);
// ANCHOR_END: array_to_asset_id
assert_eq!([1u8; 32], *asset_id);
// From a string.
// ANCHOR: string_to_asset_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let asset_id = AssetId::from_str(hex_str)?;
// ANCHOR_END: string_to_asset_id
assert_eq!([0u8; 32], *asset_id);
// ANCHOR_END: asset_id
Ok(())
}
#[tokio::test]
async fn contract_id() -> Result<()> {
// ANCHOR: contract_id
use std::str::FromStr;
use fuels::types::ContractId;
// Zeroed Bytes32
let contract_id = ContractId::zeroed();
// Grab the inner `[u8; 32]` from
// `Bytes32` by dereferencing (i.e. `*`) it.
assert_eq!([0u8; 32], *contract_id);
// From a `[u8; 32]`.
// ANCHOR: array_to_contract_id
let my_slice = [1u8; 32];
let contract_id = ContractId::new(my_slice);
// ANCHOR_END: array_to_contract_id
assert_eq!([1u8; 32], *contract_id);
// From a string.
// ANCHOR: string_to_contract_id
let hex_str = "0x0000000000000000000000000000000000000000000000000000000000000000";
let contract_id = ContractId::from_str(hex_str)?;
// ANCHOR_END: string_to_contract_id
assert_eq!([0u8; 32], *contract_id);
// ANCHOR_END: contract_id
// ANCHOR: contract_id_to_identity
let _identity_from_contract_id = Identity::ContractId(contract_id);
// ANCHOR_END: contract_id_to_identity
// ANCHOR: contract_id_to_str
let _str_from_contract_id: &str = contract_id.to_string().as_str();
// ANCHOR_END: contract_id_to_str
Ok(())
}
#[tokio::test]
async fn type_conversion() -> Result<()> {
// ANCHOR: type_conversion
use fuels::types::{AssetId, ContractId};
let contract_id = ContractId::new([1u8; 32]);
let asset_id: AssetId = AssetId::new(*contract_id);
assert_eq!([1u8; 32], *asset_id);
// ANCHOR_END: type_conversion
// ANCHOR: asset_id_to_str
let _str_from_asset_id: &str = asset_id.to_string().as_str();
// ANCHOR_END: asset_id_to_str
// ANCHOR: contract_id_to_bits256
let _contract_id_to_bits_256 = Bits256(contract_id.into());
// ANCHOR_END: contract_id_to_bits256
// ANCHOR: asset_id_to_bits256
let _asset_id_to_bits_256 = Bits256(asset_id.into());
// ANCHOR_END: asset_id_to_bits256
Ok(())
}
#[tokio::test]
async fn unused_generics() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
// ANCHOR: unused_generics_struct
assert_eq!(
<StructUnusedGeneric<u16, u32>>::new(15),
StructUnusedGeneric {
field: 15,
_unused_generic_0: std::marker::PhantomData,
_unused_generic_1: std::marker::PhantomData
}
);
// ANCHOR_END: unused_generics_struct
let my_enum = <EnumUnusedGeneric<u32, u64>>::One(15);
// ANCHOR: unused_generics_enum
match my_enum {
EnumUnusedGeneric::One(_value) => {}
EnumUnusedGeneric::IgnoreMe(..) => panic!("Will never receive this variant"),
}
// ANCHOR_END: unused_generics_enum
Ok(())
}
}
Codec
Encoding and decoding are done as per the fuel spec. To this end, fuels makes use of the ABIEncoder and the ABIDecoder.
Prerequisites for decoding/encoding
To encode a type, you must first convert it into a Token. This is commonly done by implementing the Tokenizable trait.
To decode, you also need to provide a ParamType describing the schema of the type in question. This is commonly done by implementing the Parameterize trait.
All types generated by the abigen! macro implement both the Tokenizable and Parameterize traits.
fuels also contains implementations for:
Tokenizablefor thefuels-owned types listed here as well as for some foreign types (such asu8,u16,std::vec::Vec<T: Tokenizable>, etc.).Parameterizefor thefuels-owned types listed here as well as for some foreign types (such asu8,u16,std::vec::Vec<T: Parameterize>, etc.).
Deriving the traits
Both Tokenizable and Parameterize can be derived for structs and enums if all inner types implement the derived traits:
extern crate alloc;
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[test]
fn example_of_abigen_usage() {
// ANCHOR: multiple_abigen_program_types
abigen!(
Contract(
name = "ContractA",
abi = "e2e/sway/bindings/sharing_types/contract_a/out/release/contract_a-abi.json"
),
Contract(
name = "ContractB",
abi = "e2e/sway/bindings/sharing_types/contract_b/out/release/contract_b-abi.json"
),
Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
),
Predicate(
name = "MyPredicateEncoder",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
),
);
// ANCHOR_END: multiple_abigen_program_types
}
#[test]
fn macro_deriving() {
// ANCHOR: deriving_traits
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field_a: u8,
}
#[derive(Parameterize, Tokenizable)]
enum SomeEnum {
A(MyStruct),
B(Vec<u64>),
}
// ANCHOR_END: deriving_traits
}
#[test]
fn macro_deriving_extra() {
{
use fuels::{
core as fuels_core_elsewhere,
macros::{Parameterize, Tokenizable},
types as fuels_types_elsewhere,
};
// ANCHOR: deriving_traits_paths
#[derive(Parameterize, Tokenizable)]
#[FuelsCorePath = "fuels_core_elsewhere"]
#[FuelsTypesPath = "fuels_types_elsewhere"]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_paths
}
{
// ANCHOR: deriving_traits_nostd
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
#[NoStd]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_nostd
}
}
}
Note: Deriving
Tokenizableonenums requires that all variants also implementParameterize.
Tweaking the derivation
Changing the location of imports
The derived code expects that the fuels package is accessible through ::fuels. If this is not the case then the derivation macro needs to be given the locations of fuels::types and fuels::core.
extern crate alloc;
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[test]
fn example_of_abigen_usage() {
// ANCHOR: multiple_abigen_program_types
abigen!(
Contract(
name = "ContractA",
abi = "e2e/sway/bindings/sharing_types/contract_a/out/release/contract_a-abi.json"
),
Contract(
name = "ContractB",
abi = "e2e/sway/bindings/sharing_types/contract_b/out/release/contract_b-abi.json"
),
Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
),
Predicate(
name = "MyPredicateEncoder",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
),
);
// ANCHOR_END: multiple_abigen_program_types
}
#[test]
fn macro_deriving() {
// ANCHOR: deriving_traits
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field_a: u8,
}
#[derive(Parameterize, Tokenizable)]
enum SomeEnum {
A(MyStruct),
B(Vec<u64>),
}
// ANCHOR_END: deriving_traits
}
#[test]
fn macro_deriving_extra() {
{
use fuels::{
core as fuels_core_elsewhere,
macros::{Parameterize, Tokenizable},
types as fuels_types_elsewhere,
};
// ANCHOR: deriving_traits_paths
#[derive(Parameterize, Tokenizable)]
#[FuelsCorePath = "fuels_core_elsewhere"]
#[FuelsTypesPath = "fuels_types_elsewhere"]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_paths
}
{
// ANCHOR: deriving_traits_nostd
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
#[NoStd]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_nostd
}
}
}
Generating no-std code
If you want no-std generated code:
extern crate alloc;
#[cfg(test)]
mod tests {
use fuels::prelude::*;
#[test]
fn example_of_abigen_usage() {
// ANCHOR: multiple_abigen_program_types
abigen!(
Contract(
name = "ContractA",
abi = "e2e/sway/bindings/sharing_types/contract_a/out/release/contract_a-abi.json"
),
Contract(
name = "ContractB",
abi = "e2e/sway/bindings/sharing_types/contract_b/out/release/contract_b-abi.json"
),
Script(
name = "MyScript",
abi = "e2e/sway/scripts/arguments/out/release/arguments-abi.json"
),
Predicate(
name = "MyPredicateEncoder",
abi = "e2e/sway/predicates/basic_predicate/out/release/basic_predicate-abi.json"
),
);
// ANCHOR_END: multiple_abigen_program_types
}
#[test]
fn macro_deriving() {
// ANCHOR: deriving_traits
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field_a: u8,
}
#[derive(Parameterize, Tokenizable)]
enum SomeEnum {
A(MyStruct),
B(Vec<u64>),
}
// ANCHOR_END: deriving_traits
}
#[test]
fn macro_deriving_extra() {
{
use fuels::{
core as fuels_core_elsewhere,
macros::{Parameterize, Tokenizable},
types as fuels_types_elsewhere,
};
// ANCHOR: deriving_traits_paths
#[derive(Parameterize, Tokenizable)]
#[FuelsCorePath = "fuels_core_elsewhere"]
#[FuelsTypesPath = "fuels_types_elsewhere"]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_paths
}
{
// ANCHOR: deriving_traits_nostd
use fuels::macros::{Parameterize, Tokenizable};
#[derive(Parameterize, Tokenizable)]
#[NoStd]
pub struct SomeStruct {
field_a: u64,
}
// ANCHOR_END: deriving_traits_nostd
}
}
}
Encoding
Be sure to read the prerequisites to encoding.
Encoding is done via the ABIEncoder:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
There is also a shortcut-macro that can encode multiple types which implement Tokenizable:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
Configuring the encoder
The encoder can be configured to limit its resource expenditure:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
The default values for the EncoderConfig are:
mod bounded_encoder;
use std::default::Default;
use crate::{
codec::abi_encoder::bounded_encoder::BoundedEncoder,
types::{errors::Result, Token},
};
#[derive(Debug, Clone, Copy)]
pub struct EncoderConfig {
/// Entering a struct, array, tuple, enum or vector increases the depth. Encoding will fail if
/// the current depth becomes greater than `max_depth` configured here.
pub max_depth: usize,
/// Every encoded argument will increase the token count. Encoding will fail if the current
/// token count becomes greater than `max_tokens` configured here.
pub max_tokens: usize,
}
// ANCHOR: default_encoder_config
impl Default for EncoderConfig {
fn default() -> Self {
Self {
max_depth: 45,
max_tokens: 10_000,
}
}
}
// ANCHOR_END: default_encoder_config
#[derive(Default, Clone, Debug)]
pub struct ABIEncoder {
pub config: EncoderConfig,
}
impl ABIEncoder {
pub fn new(config: EncoderConfig) -> Self {
Self { config }
}
/// Encodes `Token`s following the ABI specs defined
/// [here](https://github.com/FuelLabs/fuel-specs/blob/master/specs/protocol/abi.md)
pub fn encode(&self, tokens: &[Token]) -> Result<Vec<u8>> {
BoundedEncoder::new(self.config).encode(tokens)
}
}
#[cfg(test)]
mod tests {
use std::slice;
use super::*;
use crate::{
to_named,
types::{
errors::Error,
param_types::{EnumVariants, ParamType},
StaticStringToken, U256,
},
};
#[test]
fn encode_multiple_uint() -> Result<()> {
let tokens = [
Token::U8(u8::MAX),
Token::U16(u16::MAX),
Token::U32(u32::MAX),
Token::U64(u64::MAX),
Token::U128(u128::MAX),
Token::U256(U256::MAX),
];
let result = ABIEncoder::default().encode(&tokens)?;
let expected = [
255, // u8
255, 255, // u16
255, 255, 255, 255, // u32
255, 255, 255, 255, 255, 255, 255, 255, // u64
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, // u128
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_bool() -> Result<()> {
let token = Token::Bool(true);
let result = ABIEncoder::default().encode(&[token])?;
let expected = [1];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_b256() -> Result<()> {
let data = [
213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34,
152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11,
];
let token = Token::B256(data);
let result = ABIEncoder::default().encode(&[token])?;
assert_eq!(result, data);
Ok(())
}
#[test]
fn encode_bytes() -> Result<()> {
let token = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec());
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 0, 0, 0, 0, 7, // len
255, 0, 1, 2, 3, 4, 5, // data
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_string() -> Result<()> {
let token = Token::String("This is a full sentence".to_string());
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 0, 0, 0, 0, 23, // len
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_raw_slice() -> Result<()> {
let token = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec());
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 0, 0, 0, 0, 7, // len
255, 0, 1, 2, 3, 4, 5, // data
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_string_array() -> Result<()> {
let token = Token::StringArray(StaticStringToken::new(
"This is a full sentence".into(),
Some(23),
));
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_string_slice() -> Result<()> {
let token = Token::StringSlice(StaticStringToken::new(
"This is a full sentence".into(),
None,
));
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 0, 0, 0, 0, 23, // len
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_tuple() -> Result<()> {
let token = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]);
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 255, //u32
1, //bool
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_array() -> Result<()> {
let token = Token::Tuple(vec![Token::U32(255), Token::U32(128)]);
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 0, 0, 255, //u32
0, 0, 0, 128, //u32
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_enum_with_deeply_nested_types() -> Result<()> {
/*
enum DeeperEnum {
v1: bool,
v2: str[10]
}
*/
let types = to_named(&[ParamType::Bool, ParamType::StringArray(10)]);
let deeper_enum_variants = EnumVariants::new(types)?;
let deeper_enum_token =
Token::StringArray(StaticStringToken::new("0123456789".into(), Some(10)));
/*
struct StructA {
some_enum: DeeperEnum
some_number: u32
}
*/
let fields = to_named(&[
ParamType::Enum {
name: "".to_string(),
enum_variants: deeper_enum_variants.clone(),
generics: vec![],
},
ParamType::Bool,
]);
let struct_a_type = ParamType::Struct {
name: "".to_string(),
fields,
generics: vec![],
};
let struct_a_token = Token::Struct(vec![
Token::Enum(Box::new((1, deeper_enum_token, deeper_enum_variants))),
Token::U32(11332),
]);
/*
enum TopLevelEnum {
v1: StructA,
v2: bool,
v3: u64
}
*/
let types = to_named(&[struct_a_type, ParamType::Bool, ParamType::U64]);
let top_level_enum_variants = EnumVariants::new(types)?;
let top_level_enum_token =
Token::Enum(Box::new((0, struct_a_token, top_level_enum_variants)));
let result = ABIEncoder::default().encode(slice::from_ref(&top_level_enum_token))?;
let expected = [
0, 0, 0, 0, 0, 0, 0, 0, // TopLevelEnum::v1 discriminant
0, 0, 0, 0, 0, 0, 0, 1, // DeeperEnum::v2 discriminant
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, // str[10]
0, 0, 44, 68, // StructA.some_number
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_nested_structs() -> Result<()> {
let token = Token::Struct(vec![
Token::U16(10),
Token::Struct(vec![
Token::Bool(true),
Token::Array(vec![Token::U8(1), Token::U8(2)]),
]),
]);
let result = ABIEncoder::default().encode(&[token])?;
let expected = [
0, 10, // u16
1, // bool
1, 2, // [u8, u8]
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn encode_comprehensive() -> Result<()> {
let foo = Token::Struct(vec![
Token::U16(10),
Token::Struct(vec![
Token::Bool(true),
Token::Array(vec![Token::U8(1), Token::U8(2)]),
]),
]);
let arr_u8 = Token::Array(vec![Token::U8(1), Token::U8(2)]);
let b256 = Token::B256([255; 32]);
let str_arr = Token::StringArray(StaticStringToken::new(
"This is a full sentence".into(),
Some(23),
));
let tokens = vec![foo, arr_u8, b256, str_arr];
let result = ABIEncoder::default().encode(&tokens)?;
let expected = [
0, 10, // foo.x == 10u16
1, // foo.y.a == true
1, // foo.y.b.0 == 1u8
2, // foo.y.b.1 == 2u8
1, // u8[2].0 == 1u8
2, // u8[2].0 == 2u8
255, 255, 255, 255, 255, 255, 255, 255, // b256
255, 255, 255, 255, 255, 255, 255, 255, // b256
255, 255, 255, 255, 255, 255, 255, 255, // b256
255, 255, 255, 255, 255, 255, 255, 255, // b256
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, // str[23]
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn enums_with_only_unit_variants_are_encoded_in_one_word() -> Result<()> {
let expected = [0, 0, 0, 0, 0, 0, 0, 1];
let types = to_named(&[ParamType::Unit, ParamType::Unit]);
let enum_selector = Box::new((1, Token::Unit, EnumVariants::new(types)?));
let actual = ABIEncoder::default().encode(&[Token::Enum(enum_selector)])?;
assert_eq!(actual, expected);
Ok(())
}
#[test]
fn vec_in_enum() -> Result<()> {
// arrange
let types = to_named(&[ParamType::B256, ParamType::Vector(Box::new(ParamType::U64))]);
let variants = EnumVariants::new(types)?;
let selector = (1, Token::Vector(vec![Token::U64(5)]), variants);
let token = Token::Enum(Box::new(selector));
// act
let result = ABIEncoder::default().encode(&[token])?;
// assert
let expected = [
0, 0, 0, 0, 0, 0, 0, 1, // enum dicsriminant
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64]
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn enum_in_vec() -> Result<()> {
// arrange
let types = to_named(&[ParamType::B256, ParamType::U8]);
let variants = EnumVariants::new(types)?;
let selector = (1, Token::U8(8), variants);
let enum_token = Token::Enum(Box::new(selector));
let vec_token = Token::Vector(vec![enum_token]);
// act
let result = ABIEncoder::default().encode(&[vec_token])?;
// assert
let expected = [
0, 0, 0, 0, 0, 0, 0, 1, // vec len
0, 0, 0, 0, 0, 0, 0, 1, 8, // enum discriminant and u8 value
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn vec_in_struct() -> Result<()> {
// arrange
let token = Token::Struct(vec![Token::Vector(vec![Token::U64(5)]), Token::U8(9)]);
// act
let result = ABIEncoder::default().encode(&[token])?;
// assert
let expected = [
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, // vec[len, u64]
9, // u8
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn vec_in_vec() -> Result<()> {
// arrange
let token = Token::Vector(vec![Token::Vector(vec![Token::U8(5), Token::U8(6)])]);
// act
let result = ABIEncoder::default().encode(&[token])?;
// assert
let expected = [
0, 0, 0, 0, 0, 0, 0, 1, // vec1 len
0, 0, 0, 0, 0, 0, 0, 2, 5, 6, // vec2 [len, u8, u8]
];
assert_eq!(result, expected);
Ok(())
}
#[test]
fn max_depth_surpassed() {
const MAX_DEPTH: usize = 2;
let config = EncoderConfig {
max_depth: MAX_DEPTH,
..Default::default()
};
let msg = "depth limit `2` reached while encoding. Try increasing it".to_string();
[nested_struct, nested_enum, nested_tuple, nested_array]
.iter()
.map(|fun| fun(MAX_DEPTH + 1))
.for_each(|token| {
assert_encoding_failed(config, token, &msg);
});
}
fn assert_encoding_failed(config: EncoderConfig, token: Token, msg: &str) {
let encoder = ABIEncoder::new(config);
let err = encoder.encode(&[token]);
let Err(Error::Codec(actual_msg)) = err else {
panic!("expected a Codec error. Got: `{err:?}`");
};
assert_eq!(actual_msg, msg);
}
fn nested_struct(depth: usize) -> Token {
let fields = if depth == 1 {
vec![Token::U8(255), Token::String("bloopblip".to_string())]
} else {
vec![nested_struct(depth - 1)]
};
Token::Struct(fields)
}
fn nested_enum(depth: usize) -> Token {
if depth == 0 {
return Token::U8(255);
}
let inner_enum = nested_enum(depth - 1);
// Create a basic EnumSelector for the current level (the `EnumVariants` is not
// actually accurate but it's not used for encoding)
let selector = (
0u64,
inner_enum,
EnumVariants::new(to_named(&[ParamType::U64])).unwrap(),
);
Token::Enum(Box::new(selector))
}
fn nested_array(depth: usize) -> Token {
if depth == 1 {
Token::Array(vec![Token::U8(255)])
} else {
Token::Array(vec![nested_array(depth - 1)])
}
}
fn nested_tuple(depth: usize) -> Token {
let fields = if depth == 1 {
vec![Token::U8(255), Token::String("bloopblip".to_string())]
} else {
vec![nested_tuple(depth - 1)]
};
Token::Tuple(fields)
}
}
Configuring the encoder for contract/script calls
You can also configure the encoder used to encode the arguments of the contract method:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The same method is available for script calls.
Decoding
Be sure to read the prerequisites to decoding.
Decoding is done via the ABIDecoder:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
First into a Token, then via the Tokenizable trait, into the desired type.
If the type came from abigen! (or uses the ::fuels::macros::TryFrom derivation) then you can also use try_into to convert bytes into a type that implements both Parameterize and Tokenizable:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
Under the hood, try_from_bytes is being called, which does what the preceding example did.
Configuring the decoder
The decoder can be configured to limit its resource expenditure:
#[cfg(test)]
mod tests {
use fuels::{
core::codec::{DecoderConfig, EncoderConfig},
types::errors::Result,
};
#[test]
fn encoding_a_type() -> Result<()> {
//ANCHOR: encoding_example
use fuels::{
core::{codec::ABIEncoder, traits::Tokenizable},
macros::Tokenizable,
};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let instance = MyStruct { field: 101 };
let _encoded: Vec<u8> = ABIEncoder::default().encode(&[instance.into_token()])?;
//ANCHOR_END: encoding_example
Ok(())
}
#[test]
fn encoding_via_macro() -> Result<()> {
//ANCHOR: encoding_example_w_macro
use fuels::{core::codec::calldata, macros::Tokenizable};
#[derive(Tokenizable)]
struct MyStruct {
field: u64,
}
let _: Vec<u8> = calldata!(MyStruct { field: 101 }, MyStruct { field: 102 })?;
//ANCHOR_END: encoding_example_w_macro
Ok(())
}
#[test]
fn decoding_example() -> Result<()> {
// ANCHOR: decoding_example
use fuels::{
core::{
codec::ABIDecoder,
traits::{Parameterize, Tokenizable},
},
macros::{Parameterize, Tokenizable},
types::Token,
};
#[derive(Parameterize, Tokenizable)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let token: Token = ABIDecoder::default().decode(&MyStruct::param_type(), bytes)?;
let _: MyStruct = MyStruct::from_token(token)?;
// ANCHOR_END: decoding_example
Ok(())
}
#[test]
fn decoding_example_try_into() -> Result<()> {
// ANCHOR: decoding_example_try_into
use fuels::macros::{Parameterize, Tokenizable, TryFrom};
#[derive(Parameterize, Tokenizable, TryFrom)]
struct MyStruct {
field: u64,
}
let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 101];
let _: MyStruct = bytes.try_into()?;
// ANCHOR_END: decoding_example_try_into
Ok(())
}
#[test]
fn configuring_the_decoder() -> Result<()> {
// ANCHOR: configuring_the_decoder
use fuels::core::codec::ABIDecoder;
ABIDecoder::new(DecoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_decoder
Ok(())
}
#[test]
fn configuring_the_encoder() -> Result<()> {
// ANCHOR: configuring_the_encoder
use fuels::core::codec::ABIEncoder;
ABIEncoder::new(EncoderConfig {
max_depth: 5,
max_tokens: 100,
});
// ANCHOR_END: configuring_the_encoder
Ok(())
}
}
For an explanation of each configuration value visit the DecoderConfig.
The default values for the DecoderConfig are:
mod bounded_decoder;
mod decode_as_debug_str;
use std::io::Read;
use crate::{
codec::abi_decoder::{
bounded_decoder::BoundedDecoder, decode_as_debug_str::decode_as_debug_str,
},
types::{errors::Result, param_types::ParamType, Token},
};
#[derive(Debug, Clone, Copy)]
pub struct DecoderConfig {
/// Entering a struct, array, tuple, enum or vector increases the depth. Decoding will fail if
/// the current depth becomes greater than `max_depth` configured here.
pub max_depth: usize,
/// Every decoded Token will increase the token count. Decoding will fail if the current
/// token count becomes greater than `max_tokens` configured here.
pub max_tokens: usize,
}
// ANCHOR: default_decoder_config
impl Default for DecoderConfig {
fn default() -> Self {
Self {
max_depth: 45,
max_tokens: 10_000,
}
}
}
// ANCHOR_END: default_decoder_config
#[derive(Default)]
pub struct ABIDecoder {
pub config: DecoderConfig,
}
impl ABIDecoder {
pub fn new(config: DecoderConfig) -> Self {
Self { config }
}
/// Decodes `bytes` following the schema described in `param_type` into its respective `Token`.
///
/// # Arguments
///
/// * `param_type`: The `ParamType` of the type we expect is encoded
/// inside `bytes`.
/// * `bytes`: The bytes to be used in the decoding process.
/// # Examples
///
/// ```
/// use fuels_core::codec::ABIDecoder;
/// use fuels_core::traits::Tokenizable;
/// use fuels_core::types::param_types::ParamType;
///
/// let decoder = ABIDecoder::default();
///
/// let token = decoder.decode(&ParamType::U64, [0, 0, 0, 0, 0, 0, 0, 7].as_slice()).unwrap();
///
/// assert_eq!(u64::from_token(token).unwrap(), 7u64);
/// ```
pub fn decode(&self, param_type: &ParamType, mut bytes: impl Read) -> Result<Token> {
BoundedDecoder::new(self.config).decode(param_type, &mut bytes)
}
/// Same as `decode` but decodes multiple `ParamType`s in one go.
/// # Examples
/// ```
/// use fuels_core::codec::ABIDecoder;
/// use fuels_core::types::param_types::ParamType;
/// use fuels_core::types::Token;
///
/// let decoder = ABIDecoder::default();
/// let data = [7, 8];
///
/// let tokens = decoder.decode_multiple(&[ParamType::U8, ParamType::U8], data.as_slice()).unwrap();
///
/// assert_eq!(tokens, vec![Token::U8(7), Token::U8(8)]);
/// ```
pub fn decode_multiple(
&self,
param_types: &[ParamType],
mut bytes: impl Read,
) -> Result<Vec<Token>> {
BoundedDecoder::new(self.config).decode_multiple(param_types, &mut bytes)
}
/// Decodes `bytes` following the schema described in `param_type` into its respective debug
/// string.
///
/// # Arguments
///
/// * `param_type`: The `ParamType` of the type we expect is encoded
/// inside `bytes`.
/// * `bytes`: The bytes to be used in the decoding process.
/// # Examples
///
/// ```
/// use fuels_core::codec::ABIDecoder;
/// use fuels_core::types::param_types::ParamType;
///
/// let decoder = ABIDecoder::default();
///
/// let debug_string = decoder.decode_as_debug_str(&ParamType::U64, [0, 0, 0, 0, 0, 0, 0, 7].as_slice()).unwrap();
/// let expected_value = 7u64;
///
/// assert_eq!(debug_string, format!("{expected_value}"));
/// ```
pub fn decode_as_debug_str(
&self,
param_type: &ParamType,
mut bytes: impl Read,
) -> Result<String> {
let token = BoundedDecoder::new(self.config).decode(param_type, &mut bytes)?;
decode_as_debug_str(param_type, &token)
}
pub fn decode_multiple_as_debug_str(
&self,
param_types: &[ParamType],
mut bytes: impl Read,
) -> Result<Vec<String>> {
let token = BoundedDecoder::new(self.config).decode_multiple(param_types, &mut bytes)?;
token
.into_iter()
.zip(param_types)
.map(|(token, param_type)| decode_as_debug_str(param_type, &token))
.collect()
}
}
#[cfg(test)]
mod tests {
use std::vec;
use ParamType::*;
use super::*;
use crate::{
constants::WORD_SIZE,
to_named,
traits::Parameterize,
types::{errors::Error, param_types::EnumVariants, StaticStringToken, U256},
};
#[test]
fn decode_multiple_uint() -> Result<()> {
let types = vec![
ParamType::U8,
ParamType::U16,
ParamType::U32,
ParamType::U64,
ParamType::U128,
ParamType::U256,
];
let data = [
255, // u8
255, 255, // u16
255, 255, 255, 255, // u32
255, 255, 255, 255, 255, 255, 255, 255, // u64
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, // u128
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, // u256
];
let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?;
let expected = vec![
Token::U8(u8::MAX),
Token::U16(u16::MAX),
Token::U32(u32::MAX),
Token::U64(u64::MAX),
Token::U128(u128::MAX),
Token::U256(U256::MAX),
];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_bool() -> Result<()> {
let types = vec![ParamType::Bool, ParamType::Bool];
let data = [1, 0];
let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?;
let expected = vec![Token::Bool(true), Token::Bool(false)];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_b256() -> Result<()> {
let data = [
213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34,
152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11,
];
let decoded = ABIDecoder::default().decode(&ParamType::B256, data.as_slice())?;
assert_eq!(decoded, Token::B256(data));
Ok(())
}
#[test]
fn decode_string_array() -> Result<()> {
let types = vec![ParamType::StringArray(23), ParamType::StringArray(5)];
let data = [
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
72, 101, 108, 108, 111, // Hello
];
let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?;
let expected = vec![
Token::StringArray(StaticStringToken::new(
"This is a full sentence".into(),
Some(23),
)),
Token::StringArray(StaticStringToken::new("Hello".into(), Some(5))),
];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_string_slice() -> Result<()> {
let data = [
0, 0, 0, 0, 0, 0, 0, 23, // [length]
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
];
let decoded = ABIDecoder::default().decode(&ParamType::StringSlice, data.as_slice())?;
let expected = Token::StringSlice(StaticStringToken::new(
"This is a full sentence".into(),
None,
));
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_string() -> Result<()> {
let data = [
0, 0, 0, 0, 0, 0, 0, 23, // [length]
84, 104, 105, 115, 32, 105, 115, 32, 97, 32, 102, 117, 108, 108, 32, 115, 101, 110,
116, 101, 110, 99, 101, //This is a full sentence
];
let decoded = ABIDecoder::default().decode(&ParamType::String, data.as_slice())?;
let expected = Token::String("This is a full sentence".to_string());
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_tuple() -> Result<()> {
let param_type = ParamType::Tuple(vec![ParamType::U32, ParamType::Bool]);
let data = [
0, 0, 0, 255, //u32
1, //bool
];
let result = ABIDecoder::default().decode(¶m_type, data.as_slice())?;
let expected = Token::Tuple(vec![Token::U32(255), Token::Bool(true)]);
assert_eq!(result, expected);
Ok(())
}
#[test]
fn decode_array() -> Result<()> {
let types = vec![ParamType::Array(Box::new(ParamType::U8), 2)];
let data = [255, 42];
let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?;
let expected = vec![Token::Array(vec![Token::U8(255), Token::U8(42)])];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_struct() -> Result<()> {
// struct MyStruct {
// foo: u8,
// bar: bool,
// }
let data = [1, 1];
let param_type = ParamType::Struct {
name: "".to_string(),
fields: to_named(&[ParamType::U8, ParamType::Bool]),
generics: vec![],
};
let decoded = ABIDecoder::default().decode(¶m_type, data.as_slice())?;
let expected = Token::Struct(vec![Token::U8(1), Token::Bool(true)]);
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_bytes() -> Result<()> {
let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5];
let decoded = ABIDecoder::default().decode(&ParamType::Bytes, data.as_slice())?;
let expected = Token::Bytes([255, 0, 1, 2, 3, 4, 5].to_vec());
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_raw_slice() -> Result<()> {
let data = [0, 0, 0, 0, 0, 0, 0, 7, 255, 0, 1, 2, 3, 4, 5];
let decoded = ABIDecoder::default().decode(&ParamType::RawSlice, data.as_slice())?;
let expected = Token::RawSlice([255, 0, 1, 2, 3, 4, 5].to_vec());
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_enum() -> Result<()> {
// enum MyEnum {
// x: u32,
// y: bool,
// }
let types = to_named(&[ParamType::U32, ParamType::Bool]);
let inner_enum_types = EnumVariants::new(types)?;
let types = vec![ParamType::Enum {
name: "".to_string(),
enum_variants: inner_enum_types.clone(),
generics: vec![],
}];
let data = [
0, 0, 0, 0, 0, 0, 0, 0, // discriminant
0, 0, 0, 42, // u32
];
let decoded = ABIDecoder::default().decode_multiple(&types, data.as_slice())?;
let expected = vec![Token::Enum(Box::new((0, Token::U32(42), inner_enum_types)))];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn decode_nested_struct() -> Result<()> {
// struct Foo {
// x: u16,
// y: Bar,
// }
//
// struct Bar {
// a: bool,
// b: u8[2],
// }
let fields = to_named(&[
ParamType::U16,
ParamType::Struct {
name: "".to_string(),
fields: to_named(&[
ParamType::Bool,
ParamType::Array(Box::new(ParamType::U8), 2),
]),
generics: vec![],
},
]);
let nested_struct = ParamType::Struct {
name: "".to_string(),
fields,
generics: vec![],
};
let data = [0, 10, 1, 1, 2];
let decoded = ABIDecoder::default().decode(&nested_struct, data.as_slice())?;
let my_nested_struct = vec![
Token::U16(10),
Token::Struct(vec![
Token::Bool(true),
Token::Array(vec![Token::U8(1), Token::U8(2)]),
]),
];
assert_eq!(decoded, Token::Struct(my_nested_struct));
Ok(())
}
#[test]
fn decode_comprehensive() -> Result<()> {
// struct Foo {
// x: u16,
// y: Bar,
// }
//
// struct Bar {
// a: bool,
// b: u8[2],
// }
// fn: long_function(Foo,u8[2],b256,str[3],str)
// Parameters
let fields = to_named(&[
ParamType::U16,
ParamType::Struct {
name: "".to_string(),
fields: to_named(&[
ParamType::Bool,
ParamType::Array(Box::new(ParamType::U8), 2),
]),
generics: vec![],
},
]);
let nested_struct = ParamType::Struct {
name: "".to_string(),
fields,
generics: vec![],
};
let u8_arr = ParamType::Array(Box::new(ParamType::U8), 2);
let b256 = ParamType::B256;
let types = [nested_struct, u8_arr, b256];
let bytes = [
0, 10, // u16
1, // bool
1, 2, // array[u8;2]
1, 2, // array[u8;2]
213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34,
152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11, // b256
];
let decoded = ABIDecoder::default().decode_multiple(&types, bytes.as_slice())?;
// Expected tokens
let foo = Token::Struct(vec![
Token::U16(10),
Token::Struct(vec![
Token::Bool(true),
Token::Array(vec![Token::U8(1), Token::U8(2)]),
]),
]);
let u8_arr = Token::Array(vec![Token::U8(1), Token::U8(2)]);
let b256 = Token::B256([
213, 87, 156, 70, 223, 204, 127, 24, 32, 112, 19, 230, 91, 68, 228, 203, 78, 44, 34,
152, 244, 172, 69, 123, 168, 248, 39, 67, 243, 30, 147, 11,
]);
let expected: Vec<Token> = vec![foo, u8_arr, b256];
assert_eq!(decoded, expected);
Ok(())
}
#[test]
fn enums_with_all_unit_variants_are_decoded_from_one_word() -> Result<()> {
let data = [0, 0, 0, 0, 0, 0, 0, 1];
let types = to_named(&[ParamType::Unit, ParamType::Unit]);
let enum_variants = EnumVariants::new(types)?;
let enum_w_only_units = ParamType::Enum {
name: "".to_string(),
enum_variants: enum_variants.clone(),
generics: vec![],
};
let result = ABIDecoder::default().decode(&enum_w_only_units, data.as_slice())?;
let expected_enum = Token::Enum(Box::new((1, Token::Unit, enum_variants)));
assert_eq!(result, expected_enum);
Ok(())
}
#[test]
fn out_of_bounds_discriminant_is_detected() -> Result<()> {
let data = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2];
let types = to_named(&[ParamType::U64]);
let enum_variants = EnumVariants::new(types)?;
let enum_type = ParamType::Enum {
name: "".to_string(),
enum_variants,
generics: vec![],
};
let result = ABIDecoder::default().decode(&enum_type, data.as_slice());
let error = result.expect_err("should have resulted in an error");
let expected_msg = "discriminant `1` doesn't point to any variant: ";
assert!(matches!(error, Error::Other(str) if str.starts_with(expected_msg)));
Ok(())
}
#[test]
pub fn division_by_zero() {
let param_type = Vec::<[u16; 0]>::param_type();
let result = ABIDecoder::default().decode(¶m_type, [].as_slice());
assert!(matches!(result, Err(Error::IO(_))));
}
#[test]
pub fn multiply_overflow_enum() {
let result = ABIDecoder::default().decode(
&Enum {
name: "".to_string(),
enum_variants: EnumVariants::new(to_named(&[
Array(Box::new(Array(Box::new(RawSlice), 8)), usize::MAX),
B256,
B256,
B256,
B256,
B256,
B256,
B256,
B256,
B256,
B256,
]))
.unwrap(),
generics: vec![U16],
},
[].as_slice(),
);
assert!(matches!(result, Err(Error::IO(_))));
}
#[test]
pub fn multiply_overflow_arith() {
let mut param_type: ParamType = U16;
for _ in 0..50 {
param_type = Array(Box::new(param_type), 8);
}
let result = ABIDecoder::default().decode(
&Enum {
name: "".to_string(),
enum_variants: EnumVariants::new(to_named(&[param_type])).unwrap(),
generics: vec![U16],
},
[].as_slice(),
);
assert!(matches!(result, Err(Error::IO(_))));
}
#[test]
pub fn capacity_overflow() {
let result = ABIDecoder::default().decode(
&Array(Box::new(Array(Box::new(Tuple(vec![])), usize::MAX)), 1),
[].as_slice(),
);
assert!(matches!(result, Err(Error::Codec(_))));
}
#[test]
pub fn stack_overflow() {
let mut param_type: ParamType = U16;
for _ in 0..13500 {
param_type = Vector(Box::new(param_type));
}
let result = ABIDecoder::default().decode(¶m_type, [].as_slice());
assert!(matches!(result, Err(Error::IO(_))));
}
#[test]
pub fn capacity_malloc() {
let param_type = Array(Box::new(U8), usize::MAX);
let result = ABIDecoder::default().decode(¶m_type, [].as_slice());
assert!(matches!(result, Err(Error::IO(_))));
}
#[test]
fn max_depth_surpassed() {
const MAX_DEPTH: usize = 2;
let config = DecoderConfig {
max_depth: MAX_DEPTH,
..Default::default()
};
let msg = format!("depth limit `{MAX_DEPTH}` reached while decoding. Try increasing it");
// for each nested enum so that it may read the discriminant
let data = [0; MAX_DEPTH * WORD_SIZE];
[nested_struct, nested_enum, nested_tuple, nested_array]
.iter()
.map(|fun| fun(MAX_DEPTH + 1))
.for_each(|param_type| {
assert_decoding_failed_w_data(config, ¶m_type, &msg, data.as_slice());
})
}
#[test]
fn depth_is_not_reached() {
const MAX_DEPTH: usize = 3;
const ACTUAL_DEPTH: usize = MAX_DEPTH - 1;
// enough data to decode 2*ACTUAL_DEPTH enums (discriminant + u8 = 2*WORD_SIZE)
let data = [0; 2 * ACTUAL_DEPTH * (WORD_SIZE * 2)];
let config = DecoderConfig {
max_depth: MAX_DEPTH,
..Default::default()
};
[nested_struct, nested_enum, nested_tuple, nested_array]
.into_iter()
.map(|fun| fun(ACTUAL_DEPTH))
.map(|param_type| {
// Wrapping everything in a structure so that we may check whether the depth is
// decremented after finishing every struct field.
ParamType::Struct {
name: "".to_string(),
fields: to_named(&[param_type.clone(), param_type]),
generics: vec![],
}
})
.for_each(|param_type| {
ABIDecoder::new(config)
.decode(¶m_type, data.as_slice())
.unwrap();
})
}
#[test]
fn too_many_tokens() {
let config = DecoderConfig {
max_tokens: 3,
..Default::default()
};
{
let data = [0; 3 * WORD_SIZE];
let inner_param_types = vec![ParamType::U64; 3];
for param_type in [
ParamType::Struct {
name: "".to_string(),
fields: to_named(&inner_param_types),
generics: vec![],
},
ParamType::Tuple(inner_param_types.clone()),
ParamType::Array(Box::new(ParamType::U64), 3),
] {
assert_decoding_failed_w_data(
config,
¶m_type,
"token limit `3` reached while decoding. Try increasing it",
&data,
);
}
}
{
let data = [0, 0, 0, 0, 0, 0, 0, 3, 1, 2, 3];
assert_decoding_failed_w_data(
config,
&ParamType::Vector(Box::new(ParamType::U8)),
"token limit `3` reached while decoding. Try increasing it",
&data,
);
}
}
#[test]
fn token_count_is_being_reset_between_decodings() {
// given
let config = DecoderConfig {
max_tokens: 3,
..Default::default()
};
let param_type = ParamType::Array(Box::new(ParamType::StringArray(0)), 2);
let decoder = ABIDecoder::new(config);
decoder.decode(¶m_type, [].as_slice()).unwrap();
// when
let result = decoder.decode(¶m_type, [].as_slice());
// then
result.expect("element count to be reset");
}
fn assert_decoding_failed_w_data(
config: DecoderConfig,
param_type: &ParamType,
msg: &str,
data: &[u8],
) {
let decoder = ABIDecoder::new(config);
let err = decoder.decode(param_type, data);
let Err(Error::Codec(actual_msg)) = err else {
panic!("expected a `Codec` error. Got: `{err:?}`");
};
assert_eq!(actual_msg, msg);
}
fn nested_struct(depth: usize) -> ParamType {
let fields = if depth == 1 {
vec![]
} else {
to_named(&[nested_struct(depth - 1)])
};
ParamType::Struct {
name: "".to_string(),
fields,
generics: vec![],
}
}
fn nested_enum(depth: usize) -> ParamType {
let fields = if depth == 1 {
to_named(&[ParamType::U8])
} else {
to_named(&[nested_enum(depth - 1)])
};
ParamType::Enum {
name: "".to_string(),
enum_variants: EnumVariants::new(fields).unwrap(),
generics: vec![],
}
}
fn nested_array(depth: usize) -> ParamType {
let field = if depth == 1 {
ParamType::U8
} else {
nested_array(depth - 1)
};
ParamType::Array(Box::new(field), 1)
}
fn nested_tuple(depth: usize) -> ParamType {
let fields = if depth == 1 {
vec![ParamType::U8]
} else {
vec![nested_tuple(depth - 1)]
};
ParamType::Tuple(fields)
}
}
Configuring the decoder for contract/script calls
You can also configure the decoder used to decode the return value of the contract method:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
The same method is available for script calls.
API Reference
For a more in-depth look at the APIs provided by the Fuel Rust SDK, head over to the official documentation. In the actual Rust docs, you can see the most up-to-date information about the API, which is synced with the code as it changes.
fuels-rs Testing
note This section is still a work in progress.
Testing Basics
If you're new to Rust, you'll want to review these important tools to help you build tests.
The assert! macro
You can use the assert! macro to assert certain conditions in your test. This macro invokes panic!() and fails the test if the expression inside evaluates to false.
assert!(value == 5);
The assert_eq! macro
The assert_eq! macro works a lot like the assert macro, however instead it accepts two values, and panics if those values are not equal.
assert_eq!(balance, 100);
The assert_ne! macro
The assert_ne! macro works just like the assert_eq! macro, but it will panic if the two values are equal.
assert_ne!(address, 0);
The println! macro
You can use the println! macro to print values to the console.
println!("WALLET 1 ADDRESS {}", wallet_1.address());
println!("WALLET 1 ADDRESS {:?}", wallet_1.address());
Using {} will print the value, and using {:?} will print the value plus its type.
Using {:?} will also allow you to print values that do not have the Display trait implemented but do have the Debug trait. Alternatively you can use the dbg! macro to print these types of variables.
println!("WALLET 1 PROVIDER {:?}", wallet_1.provider().unwrap());
dbg!("WALLET 1 PROVIDER {}", wallet_1.provider().unwrap());
To print more complex types that don't have it already, you can implement your own formatted display method with the fmt module from the Rust standard library.
use std::fmt;
struct Point {
x: u64,
y: u64,
}
// add print functionality with the fmt module
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "value of x: {}, value of y: {}", self.x, self.y)
}
}
let p = Point {x: 1, y: 2};
println!("POINT: {}", p);
Run Commands
You can run your tests to see if they pass or fail with
cargo test
Outputs will be hidden if the test passes. If you want to see outputs printed from your tests regardless of whether they pass or fail, use the nocapture flag.
cargo test -- --nocapture
The setup_program_test! macro
When deploying contracts with the abigen! macro, as shown in the previous sections, the user can:
- change the default configuration parameters
- launch several providers
- create multiple wallets
- create specific assets, etc.
However, it is often the case that we want to quickly set up a test with default values and work directly with contract or script instances. The setup_program_test! can do exactly that.
Used to reduce boilerplate in integration tests. Accepts input in the form
of COMMAND(ARG...)...
COMMAND is either Wallets, Abigen, LoadScript or Deploy.
ARG is either a:
- name-value (e.g.
name="MyContract"), or, - a literal (e.g.
"some_str_literal",true,5, ...) - a sub-command (e.g.
Abigen(Contract(name="MyContract", project="some_project")))
Available COMMANDs:
Options
Example: Options(profile="debug")
Description: Sets options from ARGs to be used by other COMMANDs.
Available options:
profile: sets thecargobuild profile. Variants:"release"(default),"debug"
Cardinality: 0 or 1.
Wallets
Example: Wallets("a_wallet", "another_wallet"...)
Description: Launches a local provider and generates wallets with names taken from the provided ARGs.
Cardinality: 0 or 1.
Abigen
Example:
Abigen(
Contract(
name = "MyContract",
project = "some_folder"
),
Script(
name = "MyScript",
project = "some_folder"
),
Predicate(
name = "MyPredicate",
project = "some_folder"
),
)
Description: Generates the program bindings under the name name. project should point to root of the forc project. The project must be compiled in release mode (--release flag) for Abigen command to work.
Cardinality: 0 or N.
Deploy
Example: Deploy(name="instance_name", contract="MyContract", wallet="a_wallet")
Description: Deploys the contract (with salt) using wallet. Will create a contract instance accessible via name. Due to salt usage, the same contract can be deployed multiple times. Requires that an Abigen command be present with name equal to contract. wallet can either be one of the wallets in the Wallets COMMAND or the name of a wallet you've previously generated yourself.
Cardinality: 0 or N.
LoadScript
Example: LoadScript(name = "script_instance", script = "MyScript", wallet = "wallet")
Description: Creates a script instance of script under name using wallet.
Cardinality: 0 or N.
The setup code that you have seen in previous sections gets reduced to:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
Note The same contract can be deployed several times as the macro deploys the contracts with salt. You can also deploy different contracts to the same provider by referencing the same wallet in the
Deploycommand.
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
In this example, three contracts are deployed on the same provider using the wallet generated by the Wallets command. The second and third macros use the same contract but have different IDs because of the deployment with salt. Both of them can call the first contract by using their ID.
In addition, you can manually create the wallet variable and then use it inside the macro. This is useful if you want to create custom wallets or providers but still want to use the macro to reduce boilerplate code. Below is an example of this approach.
use std::time::Duration;
use fuel_tx::{
consensus_parameters::{ConsensusParametersV1, FeeParametersV1},
ConsensusParameters, FeeParameters, Output,
};
use fuels::{
core::codec::{calldata, encode_fn_selector, DecoderConfig, EncoderConfig},
prelude::*,
programs::DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
tx::ContractParameters,
types::{errors::transaction::Reason, input::Input, Bits256, Identity},
};
use tokio::time::Instant;
#[tokio::test]
async fn test_multiple_args() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Make sure we can call the contract with multiple arguments
let contract_methods = contract_instance.methods();
let response = contract_methods.get(5, 6).call().await?;
assert_eq!(response.value, 11);
let t = MyType { x: 5, y: 6 };
let response = contract_methods.get_alt(t.clone()).call().await?;
assert_eq!(response.value, t);
let response = contract_methods.get_single(5).call().await?;
assert_eq!(response.value, 5);
Ok(())
}
#[tokio::test]
async fn test_contract_calling_contract() -> Result<()> {
// Tests a contract call that calls another contract (FooCaller calls FooContract underneath)
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "lib_contract_instance2",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let lib_contract_id2 = lib_contract_instance2.contract_id();
// Call the contract directly. It increments the given value.
let response = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, response.value);
let response = contract_caller_instance
.methods()
.increment_from_contracts(lib_contract_id, lib_contract_id2, 42)
// Note that the two lib_contract_instances have different types
.with_contracts(&[&lib_contract_instance, &lib_contract_instance2])
.call()
.await?;
assert_eq!(86, response.value);
// ANCHOR: external_contract
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
// ANCHOR_END: external_contract
assert_eq!(43, response.value);
// ANCHOR: external_contract_ids
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contract_ids(&[lib_contract_id.clone()])
.call()
.await?;
// ANCHOR_END: external_contract_ids
assert_eq!(43, response.value);
Ok(())
}
#[tokio::test]
async fn test_reverting_transaction() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "RevertContract",
project = "e2e/sway/contracts/revert_transaction_error"
)),
Deploy(
name = "contract_instance",
contract = "RevertContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance
.methods()
.make_transaction_fail(true)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { revert_id, .. })) if revert_id == 128
));
Ok(())
}
#[tokio::test]
async fn test_multiple_read_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MultiReadContract",
project = "e2e/sway/contracts/multiple_read_calls"
)),
Deploy(
name = "contract_instance",
contract = "MultiReadContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
contract_methods.store(42).call().await?;
// Use "simulate" because the methods don't actually
// run a transaction, but just a dry-run
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
let stored = contract_methods
.read()
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(stored.value, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_beginner() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (val_1, val_2): (u64, u64) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_multi_call_pro() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let my_type_1 = MyType { x: 1, y: 2 };
let my_type_2 = MyType { x: 3, y: 4 };
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(5);
let call_handler_2 = contract_methods.get_single(6);
let call_handler_3 = contract_methods.get_alt(my_type_1.clone());
let call_handler_4 = contract_methods.get_alt(my_type_2.clone());
let call_handler_5 = contract_methods.get_array([7; 2]);
let call_handler_6 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3)
.add_call(call_handler_4)
.add_call(call_handler_5)
.add_call(call_handler_6);
let (val_1, val_2, type_1, type_2, array_1, array_2): (
u64,
u64,
MyType,
MyType,
[u64; 2],
[u64; 2],
) = multi_call_handler.call().await?.value;
assert_eq!(val_1, 5);
assert_eq!(val_2, 6);
assert_eq!(type_1, my_type_1);
assert_eq!(type_2, my_type_2);
assert_eq!(array_1, [7; 2]);
assert_eq!(array_2, [42; 2]);
Ok(())
}
#[tokio::test]
async fn test_contract_call_fee_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_limit = 800;
let tolerance = Some(0.2);
let block_horizon = Some(1);
let expected_gas_used = 960;
let expected_metered_bytes_size = 824;
let estimated_transaction_cost = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.estimate_transaction_cost(tolerance, block_horizon)
.await?;
assert_eq!(estimated_transaction_cost.gas_used, expected_gas_used);
assert_eq!(
estimated_transaction_cost.metered_bytes_size,
expected_metered_bytes_size
);
Ok(())
}
#[tokio::test]
async fn contract_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = contract_methods
.initialize_counter(42)
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = contract_methods
.initialize_counter(42)
.call()
.await?
.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn mult_call_has_same_estimated_and_used_gas() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let estimated_gas_used = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon)
.await?
.gas_used;
let gas_used = multi_call_handler.call::<(u64, [u64; 2])>().await?.gas_used;
assert_eq!(estimated_gas_used, gas_used);
Ok(())
}
#[tokio::test]
async fn contract_method_call_respects_maturity() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "BlockHeightContract",
project = "e2e/sway/contracts/transaction_block_height"
)),
Deploy(
name = "contract_instance",
contract = "BlockHeightContract",
wallet = "wallet",
random_salt = false,
),
);
let call_w_maturity = |maturity| {
contract_instance
.methods()
.calling_this_will_produce_a_block()
.with_tx_policies(TxPolicies::default().with_maturity(maturity))
};
call_w_maturity(1).call().await.expect(
"should have passed since we're calling with a maturity \
that is less or equal to the current block height",
);
call_w_maturity(3).call().await.expect_err(
"should have failed since we're calling with a maturity \
that is greater than the current block height",
);
Ok(())
}
#[tokio::test]
async fn test_auth_msg_sender_from_sdk() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "AuthContract",
project = "e2e/sway/contracts/auth_testing_contract"
)),
Deploy(
name = "contract_instance",
contract = "AuthContract",
wallet = "wallet",
random_salt = false,
),
);
// Contract returns true if `msg_sender()` matches `wallet.address()`.
let response = contract_instance
.methods()
.check_msg_sender(wallet.address())
.call()
.await?;
assert!(response.value);
Ok(())
}
#[tokio::test]
async fn test_large_return_data() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/large_return_data"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let res = contract_methods.get_id().call().await?;
assert_eq!(
res.value.0,
[
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
]
);
// One word-sized string
let res = contract_methods.get_small_string().call().await?;
assert_eq!(res.value, "gggggggg");
// Two word-sized string
let res = contract_methods.get_large_string().call().await?;
assert_eq!(res.value, "ggggggggg");
// Large struct will be bigger than a `WORD`.
let res = contract_methods.get_large_struct().call().await?;
assert_eq!(res.value.foo, 12);
assert_eq!(res.value.bar, 42);
// Array will be returned in `ReturnData`.
let res = contract_methods.get_large_array().call().await?;
assert_eq!(res.value, [1, 2]);
let res = contract_methods.get_contract_id().call().await?;
// First `value` is from `CallResponse`.
// Second `value` is from the `ContractId` type.
assert_eq!(
res.value,
ContractId::from([
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255
])
);
Ok(())
}
#[tokio::test]
async fn can_handle_function_called_new() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().new().call().await?.value;
assert_eq!(response, 12345);
Ok(())
}
#[tokio::test]
async fn test_contract_setup_macro_deploy_with_salt() -> Result<()> {
// ANCHOR: contract_setup_macro_multi
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
),
Deploy(
name = "contract_caller_instance2",
contract = "LibContractCaller",
wallet = "wallet",
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_caller_id = contract_caller_instance.contract_id();
let contract_caller_id2 = contract_caller_instance2.contract_id();
// Because we deploy with salt, we can deploy the same contract multiple times
assert_ne!(contract_caller_id, contract_caller_id2);
// The first contract can be called because they were deployed on the same provider
let response = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
let response = contract_caller_instance2
.methods()
.increment_from_contract(lib_contract_id, 42)
.with_contracts(&[&lib_contract_instance])
.call()
.await?;
assert_eq!(43, response.value);
// ANCHOR_END: contract_setup_macro_multi
Ok(())
}
#[tokio::test]
async fn test_wallet_getter() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
assert_eq!(contract_instance.account().address(), wallet.address());
//`contract_id()` is tested in
// async fn test_contract_calling_contract() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_connect_wallet() -> Result<()> {
// ANCHOR: contract_setup_macro_manual_wallet
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// ANCHOR_END: contract_setup_macro_manual_wallet
// pay for call with wallet
let tx_policies = TxPolicies::default()
.with_tip(100)
.with_script_gas_limit(1_000_000);
contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm that funds have been deducted
let wallet_balance = wallet.get_asset_balance(&Default::default()).await?;
assert!(DEFAULT_COIN_AMOUNT > wallet_balance);
// pay for call with wallet_2
contract_instance
.with_account(wallet_2.clone())
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
// confirm there are no changes to wallet, wallet_2 has been charged
let wallet_balance_second_call = wallet.get_asset_balance(&Default::default()).await?;
let wallet_2_balance = wallet_2.get_asset_balance(&Default::default()).await?;
assert_eq!(wallet_balance_second_call, wallet_balance);
assert!(DEFAULT_COIN_AMOUNT > wallet_2_balance);
Ok(())
}
async fn setup_output_variable_estimation_test() -> Result<(
Vec<WalletUnlocked>,
[Identity; 3],
AssetId,
Bech32ContractId,
)> {
let wallet_config = WalletsConfig::new(Some(3), None, None);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let contract_id = Contract::load_from(
"sway/contracts/token_ops/out/release/token_ops.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let mint_asset_id = contract_id.asset_id(&Bits256::zeroed());
let addresses = wallets
.iter()
.map(|wallet| wallet.address().into())
.collect::<Vec<_>>()
.try_into()
.unwrap();
Ok((wallets, addresses, mint_asset_id, contract_id))
}
#[tokio::test]
async fn test_output_variable_estimation() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let contract_methods = contract_instance.methods();
let amount = 1000;
{
// Should fail due to lack of output variables
let response = contract_methods
.mint_to_addresses(amount, addresses)
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
{
// Should add 3 output variables automatically
let _ = contract_methods
.mint_to_addresses(amount, addresses)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, amount);
}
}
Ok(())
}
#[tokio::test]
async fn test_output_variable_estimation_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let (wallets, addresses, mint_asset_id, contract_id) =
setup_output_variable_estimation_test().await?;
let contract_instance = MyContract::new(contract_id.clone(), wallets[0].clone());
let contract_methods = contract_instance.methods();
const NUM_OF_CALLS: u64 = 3;
let amount = 1000;
let total_amount = amount * NUM_OF_CALLS;
let mut multi_call_handler = CallHandler::new_multi_call(wallets[0].clone());
for _ in 0..NUM_OF_CALLS {
let call_handler = contract_methods.mint_to_addresses(amount, addresses);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
wallets[0]
.force_transfer_to_contract(
&contract_id,
total_amount,
AssetId::zeroed(),
TxPolicies::default(),
)
.await
.unwrap();
let base_layer_address = Bits256([1u8; 32]);
let call_handler = contract_methods.send_message(base_layer_address, amount);
multi_call_handler = multi_call_handler.add_call(call_handler);
let _ = multi_call_handler
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call::<((), (), ())>()
.await?;
for wallet in wallets.iter() {
let balance = wallet.get_asset_balance(&mint_asset_id).await?;
assert_eq!(balance, 3 * amount);
}
Ok(())
}
#[tokio::test]
async fn test_contract_instance_get_balances() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let (coins, asset_ids) = setup_multiple_assets_coins(wallet.address(), 2, 4, 8);
let random_asset_id = &asset_ids[1];
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
// Check the current balance of the contract with id 'contract_id'
let contract_balances = contract_instance.get_balances().await?;
assert!(contract_balances.is_empty());
// Transfer an amount to the contract
let amount = 8;
wallet
.force_transfer_to_contract(contract_id, amount, *random_asset_id, TxPolicies::default())
.await?;
// Check that the contract now has 1 coin
let contract_balances = contract_instance.get_balances().await?;
assert_eq!(contract_balances.len(), 1);
let random_asset_balance = contract_balances.get(random_asset_id).unwrap();
assert_eq!(*random_asset_balance, amount);
Ok(())
}
#[tokio::test]
async fn contract_call_futures_implement_send() -> Result<()> {
use std::future::Future;
fn tokio_spawn_imitation<T>(_: T)
where
T: Future + Send + 'static,
{
}
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
tokio_spawn_imitation(async move {
contract_instance
.methods()
.initialize_counter(42)
.call()
.await
.unwrap();
});
Ok(())
}
#[tokio::test]
async fn test_contract_set_estimation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let res = lib_contract_instance.methods().increment(42).call().await?;
assert_eq!(43, res.value);
{
// Should fail due to missing external contracts
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.call()
.await;
assert!(matches!(
res,
Err(Error::Transaction(Reason::Reverted { .. }))
));
}
let res = contract_caller_instance
.methods()
.increment_from_contract(lib_contract_id, 42)
.determine_missing_contracts(None)
.await?
.call()
.await?;
assert_eq!(43, res.value);
Ok(())
}
#[tokio::test]
async fn test_output_variable_contract_id_estimation_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "LibContract",
project = "e2e/sway/contracts/lib_contract"
),
Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "lib_contract_instance",
contract = "LibContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_test_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let lib_contract_id = lib_contract_instance.contract_id();
let contract_methods = contract_caller_instance.methods();
let mut multi_call_handler =
CallHandler::new_multi_call(wallet.clone()).with_tx_policies(Default::default());
for _ in 0..3 {
let call_handler = contract_methods.increment_from_contract(lib_contract_id, 42);
multi_call_handler = multi_call_handler.add_call(call_handler);
}
// add call that does not need ContractId
let contract_methods = contract_test_instance.methods();
let call_handler = contract_methods.get(5, 6);
multi_call_handler = multi_call_handler.add_call(call_handler);
let call_response = multi_call_handler
.determine_missing_contracts(None)
.await?
.call::<(u64, u64, u64, u64)>()
.await?;
assert_eq!(call_response.value, (43, 43, 43, 11));
Ok(())
}
#[tokio::test]
async fn test_contract_call_with_non_default_max_input() -> Result<()> {
use fuels::{
tx::{ConsensusParameters, TxParameters},
types::coin::Coin,
};
let mut consensus_parameters = ConsensusParameters::default();
let tx_params = TxParameters::default()
.with_max_inputs(123)
.with_max_size(1_000_000);
consensus_parameters.set_tx_params(tx_params);
let contract_params = ContractParameters::default().with_contract_max_size(1_000_000);
consensus_parameters.set_contract_params(contract_params);
let mut wallet = WalletUnlocked::new_random(None);
let coins: Vec<Coin> = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let chain_config = ChainConfig {
consensus_parameters: consensus_parameters.clone(),
..ChainConfig::default()
};
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
assert_eq!(consensus_parameters, provider.consensus_parameters().await?);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let response = contract_instance.methods().get(5, 6).call().await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn test_add_custom_assets() -> Result<()> {
let initial_amount = 100_000;
let asset_base = AssetConfig {
id: AssetId::zeroed(),
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_1 = AssetId::from([3u8; 32]);
let asset_1 = AssetConfig {
id: asset_id_1,
num_coins: 1,
coin_amount: initial_amount,
};
let asset_id_2 = AssetId::from([1u8; 32]);
let asset_2 = AssetConfig {
id: asset_id_2,
num_coins: 1,
coin_amount: initial_amount,
};
let assets = vec![asset_base, asset_1, asset_2];
let num_wallets = 2;
let wallet_config = WalletsConfig::new_multiple_assets(num_wallets, assets);
let mut wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet_1",
random_salt = false,
),
);
let amount_1 = 5000;
let amount_2 = 3000;
let response = contract_instance
.methods()
.get(5, 6)
.add_custom_asset(asset_id_1, amount_1, Some(wallet_2.address().clone()))
.add_custom_asset(asset_id_2, amount_2, Some(wallet_2.address().clone()))
.call()
.await?;
assert_eq!(response.value, 11);
let balance_asset_1 = wallet_1.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_1.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount - amount_1);
assert_eq!(balance_asset_2, initial_amount - amount_2);
let balance_asset_1 = wallet_2.get_asset_balance(&asset_id_1).await?;
let balance_asset_2 = wallet_2.get_asset_balance(&asset_id_2).await?;
assert_eq!(balance_asset_1, initial_amount + amount_1);
assert_eq!(balance_asset_2, initial_amount + amount_2);
Ok(())
}
#[tokio::test]
async fn contract_load_error_messages() {
{
let binary_path = "sway/contracts/contract_test/out/release/no_file_on_path.bin";
let expected_error = format!("io: file \"{binary_path}\" does not exist");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
{
let binary_path = "sway/contracts/contract_test/out/release/contract_test-abi.json";
let expected_error = format!("expected \"{binary_path}\" to have '.bin' extension");
let error = Contract::load_from(binary_path, LoadConfiguration::default())
.expect_err("should have failed");
assert_eq!(error.to_string(), expected_error);
}
}
#[tokio::test]
async fn test_payable_annotation() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/payable_annotation"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let response = contract_methods
.payable()
.call_params(
CallParameters::default()
.with_amount(100)
.with_gas_forwarded(20_000),
)?
.call()
.await?;
assert_eq!(response.value, 42);
// ANCHOR: non_payable_params
let err = contract_methods
.non_payable()
.call_params(CallParameters::default().with_amount(100))
.expect_err("should return error");
assert!(matches!(err, Error::Other(s) if s.contains("assets forwarded to non-payable method")));
// ANCHOR_END: non_payable_params
let response = contract_methods
.non_payable()
.call_params(CallParameters::default().with_gas_forwarded(20_000))?
.call()
.await?;
assert_eq!(response.value, 42);
Ok(())
}
#[tokio::test]
async fn multi_call_from_calls_with_different_account_types() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = WalletUnlocked::new_random(None);
let predicate = Predicate::from_code(vec![]);
let contract_methods_wallet =
MyContract::new(Bech32ContractId::default(), wallet.clone()).methods();
let contract_methods_predicate =
MyContract::new(Bech32ContractId::default(), predicate).methods();
let call_handler_1 = contract_methods_wallet.initialize_counter(42);
let call_handler_2 = contract_methods_predicate.get_array([42; 2]);
let _multi_call_handler = CallHandler::new_multi_call(wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
Ok(())
}
#[tokio::test]
async fn low_level_call() -> Result<()> {
use fuels::types::SizedAsciiString;
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet",
random_salt = false,
),
);
let function_selector = encode_fn_selector("initialize_counter");
let call_data = calldata!(42u64)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let response = target_contract_instance
.methods()
.get_counter()
.call()
.await?;
assert_eq!(response.value, 42);
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
let result_uint = target_contract_instance
.methods()
.get_counter()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 42);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
#[test]
fn db_rocksdb() {
use std::{fs, str::FromStr};
use fuels::{
accounts::wallet::WalletUnlocked,
client::{PageDirection, PaginationRequest},
crypto::SecretKey,
prelude::{setup_test_provider, DbType, Error, ViewOnlyAccount, DEFAULT_COIN_AMOUNT},
};
let temp_dir = tempfile::tempdir().expect("failed to make tempdir");
let temp_dir_name = temp_dir
.path()
.file_name()
.expect("failed to get file name")
.to_string_lossy()
.to_string();
let temp_database_path = temp_dir.path().join("db");
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let _ = temp_dir;
let wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
const NUMBER_OF_ASSETS: u64 = 2;
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let chain_config = ChainConfig {
chain_name: temp_dir_name.clone(),
consensus_parameters: Default::default(),
..ChainConfig::local_testnet()
};
let (coins, _) = setup_multiple_assets_coins(
wallet.address(),
NUMBER_OF_ASSETS,
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider =
setup_test_provider(coins.clone(), vec![], Some(node_config), Some(chain_config))
.await?;
provider.produce_blocks(2, None).await?;
Ok::<(), Error>(())
})
.unwrap();
// The runtime needs to be terminated because the node can currently only be killed when the runtime itself shuts down.
tokio::runtime::Runtime::new()
.expect("tokio runtime failed")
.block_on(async {
let node_config = NodeConfig {
database_type: DbType::RocksDb(Some(temp_database_path.clone())),
..NodeConfig::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
// the same wallet that was used when rocksdb was built. When we connect it to the provider, we expect it to have the same amount of assets
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
wallet.set_provider(provider.clone());
let blocks = provider
.get_blocks(PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
})
.await?
.results;
assert_eq!(blocks.len(), 3);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(
*wallet.get_balances().await?.iter().next().unwrap().1,
DEFAULT_COIN_AMOUNT as u128
);
assert_eq!(wallet.get_balances().await?.len(), 2);
fs::remove_dir_all(
temp_database_path
.parent()
.expect("db parent folder does not exist"),
)?;
Ok::<(), Error>(())
})
.unwrap();
}
#[tokio::test]
async fn can_configure_decoding_of_contract_return() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/needs_custom_decoder"
),),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let methods = contract_instance.methods();
{
// Single call: Will not work if max_tokens not big enough
methods.i_return_a_1k_el_array().with_decoder_config(DecoderConfig{max_tokens: 100, ..Default::default()}).call().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Single call: Works when limit is bumped
let result = methods
.i_return_a_1k_el_array()
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call()
.await?
.value;
assert_eq!(result, [0; 1000]);
}
{
// Multi call: Will not work if max_tokens not big enough
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig { max_tokens: 100, ..Default::default() })
.call::<([u8; 1000],)>().await.expect_err(
"should have failed because there are more tokens than what is supported by default",
);
}
{
// Multi call: Works when configured
CallHandler::new_multi_call(wallet.clone())
.add_call(methods.i_return_a_1k_el_array())
.with_decoder_config(DecoderConfig {
max_tokens: 1001,
..Default::default()
})
.call::<([u8; 1000],)>()
.await
.unwrap();
}
Ok(())
}
#[tokio::test]
async fn test_contract_submit_and_response() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_methods = contract_instance.methods();
let submitted_tx = contract_methods.get(1, 2).submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = submitted_tx.response().await?.value;
assert_eq!(value, 3);
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get_single(7);
let call_handler_2 = contract_methods.get_single(42);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let handle = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (val_1, val_2): (u64, u64) = handle.response().await?.value;
assert_eq!(val_1, 7);
assert_eq!(val_2, 42);
Ok(())
}
#[tokio::test]
async fn test_heap_type_multicall() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vector_output"
)
),
Deploy(
name = "contract_instance",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
{
let call_handler_1 = contract_instance.methods().u8_in_vec(5);
let call_handler_2 = contract_instance_2.methods().get_single(7);
let call_handler_3 = contract_instance.methods().u8_in_vec(3);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2)
.add_call(call_handler_3);
let (val_1, val_2, val_3): (Vec<u8>, u64, Vec<u8>) = multi_call_handler.call().await?.value;
assert_eq!(val_1, vec![0, 1, 2, 3, 4]);
assert_eq!(val_2, 7);
assert_eq!(val_3, vec![0, 1, 2]);
}
Ok(())
}
#[tokio::test]
async fn heap_types_correctly_offset_in_create_transactions_w_storage_slots() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Predicate(
name = "MyPredicate",
project = "e2e/sway/types/predicates/predicate_vector"
),),
);
let provider = wallet.try_provider()?.clone();
let data = MyPredicateEncoder::default().encode_data(18, 24, vec![2, 4, 42])?;
let predicate = Predicate::load_from(
"sway/types/predicates/predicate_vector/out/release/predicate_vector.bin",
)?
.with_data(data)
.with_provider(provider);
wallet
.transfer(
predicate.address(),
10_000,
AssetId::zeroed(),
TxPolicies::default(),
)
.await?;
// if the contract is successfully deployed then the predicate was unlocked. This further means
// the offsets were setup correctly since the predicate uses heap types in its arguments.
// Storage slots were loaded automatically by default
Contract::load_from(
"sway/contracts/storage/out/release/storage.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&predicate, TxPolicies::default())
.await?;
Ok(())
}
#[tokio::test]
async fn test_arguments_with_gas_forwarded() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
),
Contract(
name = "VectorOutputContract",
project = "e2e/sway/types/contracts/vectors"
)
),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
Deploy(
name = "contract_instance_2",
contract = "VectorOutputContract",
wallet = "wallet",
random_salt = false,
),
);
let x = 128;
let vec_input = vec![0, 1, 2];
{
let response = contract_instance
.methods()
.get_single(x)
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
assert_eq!(response.value, x);
}
{
contract_instance_2
.methods()
.u32_vec(vec_input.clone())
.call_params(CallParameters::default().with_gas_forwarded(4096))?
.call()
.await?;
}
{
let call_handler_1 = contract_instance.methods().get_single(x);
let call_handler_2 = contract_instance_2.methods().u32_vec(vec_input);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let (value, _): (u64, ()) = multi_call_handler.call().await?.value;
assert_eq!(value, x);
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call_no_signatures_strategy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let counter = 42;
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
let base_asset_id = *provider.consensus_parameters().await?.base_asset_id();
let amount = 10;
let consensus_parameters = provider.consensus_parameters().await?;
let new_base_inputs = wallet
.get_asset_inputs_for_amount(base_asset_id, amount, None)
.await?;
tb.inputs_mut().extend(new_base_inputs);
tb.outputs_mut()
.push(Output::change(wallet.address().into(), 0, base_asset_id));
// ANCHOR: tb_no_signatures_strategy
let mut tx = tb
.with_build_strategy(ScriptBuildStrategy::NoSignatures)
.build(provider)
.await?;
// ANCHOR: tx_sign_with
tx.sign_with(&wallet, consensus_parameters.chain_id())
.await?;
// ANCHOR_END: tx_sign_with
// ANCHOR_END: tb_no_signatures_strategy
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
Ok(())
}
#[tokio::test]
async fn contract_encoder_config_is_applied() -> Result<()> {
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet")
);
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let instance = TestContract::new(contract_id.clone(), wallet.clone());
{
let _encoding_ok = instance
.methods()
.get(0, 1)
.call()
.await
.expect("should not fail as it uses the default encoder config");
}
{
let encoder_config = EncoderConfig {
max_tokens: 1,
..Default::default()
};
let instance_with_encoder_config = instance.with_encoder_config(encoder_config);
// uses 2 tokens when 1 is the limit
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.call()
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
let encoding_error = instance_with_encoder_config
.methods()
.get(0, 1)
.simulate(Execution::Realistic)
.await
.expect_err("should error");
assert!(encoding_error.to_string().contains(
"cannot encode contract call arguments: codec: token limit `1` reached while encoding."
));
}
Ok(())
}
#[tokio::test]
async fn test_reentrant_calls() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "LibContractCaller",
project = "e2e/sway/contracts/lib_contract_caller"
),),
Deploy(
name = "contract_caller_instance",
contract = "LibContractCaller",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_caller_instance.contract_id();
let response = contract_caller_instance
.methods()
.re_entrant(contract_id, true)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn msg_sender_gas_estimation_issue() {
// Gas estimation requires an input of the base asset. If absent, a fake input is
// added. However, if a non-base coin is present and the fake input introduces a
// second owner, it causes the `msg_sender` sway fn to fail. This leads
// to a premature failure in gas estimation, risking transaction failure due to
// a low gas limit.
let mut wallet = WalletUnlocked::new_random(None);
let (coins, ids) =
setup_multiple_assets_coins(wallet.address(), 2, DEFAULT_NUM_COINS, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None)
.await
.unwrap();
wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/msg_methods"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let asset_id = ids[0];
// The fake coin won't be added if we add a base asset, so let's not do that
assert!(
asset_id
!= *provider
.consensus_parameters()
.await
.unwrap()
.base_asset_id()
);
let call_params = CallParameters::default()
.with_amount(100)
.with_asset_id(asset_id);
contract_instance
.methods()
.message_sender()
.call_params(call_params)
.unwrap()
.call()
.await
.unwrap();
}
#[tokio::test]
async fn variable_output_estimation_is_optimized() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/var_outputs"
)),
Deploy(
contract = "MyContract",
name = "contract_instance",
wallet = "wallet",
random_salt = false,
)
);
let contract_methods = contract_instance.methods();
let coins = 252;
let recipient = Identity::Address(wallet.address().into());
let start = Instant::now();
let _ = contract_methods
.mint(coins, recipient)
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.call()
.await?;
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary)
// we won't validate in that case so we don't have to maintain two expectations
if !cfg!(debug_assertions) {
let elapsed = start.elapsed().as_secs();
let limit = 2;
if elapsed > limit {
panic!("Estimation took too long ({elapsed}). Limit is {limit}");
}
}
Ok(())
}
async fn setup_node_with_high_price() -> Result<Vec<WalletUnlocked>> {
let wallet_config = WalletsConfig::new(None, None, None);
let fee_parameters = FeeParameters::V1(FeeParametersV1 {
gas_price_factor: 92000,
gas_per_byte: 63,
});
let consensus_parameters = ConsensusParameters::V1(ConsensusParametersV1 {
fee_params: fee_parameters,
..Default::default()
});
let node_config = Some(NodeConfig {
starting_gas_price: 1100,
..NodeConfig::default()
});
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(wallet_config, node_config, Some(chain_config))
.await?;
Ok(wallets)
}
#[tokio::test]
async fn simulations_can_be_made_without_coins() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let response = MyContract::new(contract_id, no_funds_wallet.clone())
.methods()
.get(5, 6)
.simulate(Execution::StateReadOnly)
.await?;
assert_eq!(response.value, 11);
Ok(())
}
#[tokio::test]
async fn simulations_can_be_made_without_coins_multicall() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets = setup_node_with_high_price().await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let provider = wallet.provider().cloned();
let no_funds_wallet = WalletUnlocked::new_random(provider);
let contract_instance = MyContract::new(contract_id, no_funds_wallet.clone());
let contract_methods = contract_instance.methods();
let call_handler_1 = contract_methods.get(1, 2);
let call_handler_2 = contract_methods.get(3, 4);
let mut multi_call_handler = CallHandler::new_multi_call(no_funds_wallet)
.add_call(call_handler_1)
.add_call(call_handler_2);
let value: (u64, u64) = multi_call_handler
.simulate(Execution::StateReadOnly)
.await?
.value;
assert_eq!(value, (3, 7));
Ok(())
}
#[tokio::test]
async fn contract_call_with_non_zero_base_asset_id_and_tip() -> Result<()> {
use fuels::{prelude::*, tx::ConsensusParameters};
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let asset_id = AssetId::new([1; 32]);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_base_asset_id(asset_id);
let config = ChainConfig {
consensus_parameters,
..Default::default()
};
let asset_base = AssetConfig {
id: asset_id,
num_coins: 1,
coin_amount: 10_000,
};
let wallet_config = WalletsConfig::new_multiple_assets(1, vec![asset_base]);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, Some(config)).await?;
let wallet = wallets.first().expect("has wallet");
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_tip(10))
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn max_fee_estimation_respects_tolerance() -> Result<()> {
use fuels::prelude::*;
let mut call_wallet = WalletUnlocked::new_random(None);
let call_coins = setup_single_asset_coins(call_wallet.address(), AssetId::BASE, 1000, 1);
let mut deploy_wallet = WalletUnlocked::new_random(None);
let deploy_coins =
setup_single_asset_coins(deploy_wallet.address(), AssetId::BASE, 1, 1_000_000);
let provider =
setup_test_provider([call_coins, deploy_coins].concat(), vec![], None, None).await?;
call_wallet.set_provider(provider.clone());
deploy_wallet.set_provider(provider.clone());
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
wallet = "deploy_wallet",
contract = "MyContract",
random_salt = false,
)
);
let contract_instance = contract_instance.with_account(call_wallet.clone());
let max_fee_from_tx = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
let builder = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap();
assert_eq!(
builder.max_fee_estimation_tolerance, DEFAULT_MAX_FEE_ESTIMATION_TOLERANCE,
"Expected pre-set tolerance"
);
builder
.with_max_fee_estimation_tolerance(tolerance)
.build(&provider)
.await
.unwrap()
.max_fee()
.unwrap()
}
};
let max_fee_from_builder = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let provider = provider.clone();
async move {
contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance)
.estimate_max_fee(&provider)
.await
.unwrap()
}
};
let base_amount_in_inputs = |tolerance: f32| {
let contract_instance = contract_instance.clone();
let call_wallet = &call_wallet;
async move {
let mut tb = contract_instance
.methods()
.initialize_counter(42)
.transaction_builder()
.await
.unwrap()
.with_max_fee_estimation_tolerance(tolerance);
call_wallet.adjust_for_fee(&mut tb, 0).await.unwrap();
tb.inputs
.iter()
.filter_map(|input: &Input| match input {
Input::ResourceSigned { resource }
if resource.coin_asset_id().unwrap() == AssetId::BASE =>
{
Some(resource.amount())
}
_ => None,
})
.sum::<u64>()
}
};
let no_increase_max_fee = max_fee_from_tx(0.0).await;
let increased_max_fee = max_fee_from_tx(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let no_increase_max_fee = max_fee_from_builder(0.0).await;
let increased_max_fee = max_fee_from_builder(2.00).await;
assert_eq!(
increased_max_fee as f64 / no_increase_max_fee as f64,
1.00 + 2.00
);
let normal_base_asset = base_amount_in_inputs(0.0).await;
let more_base_asset_due_to_bigger_tolerance = base_amount_in_inputs(5.00).await;
assert!(more_base_asset_due_to_bigger_tolerance > normal_base_asset);
Ok(())
}
#[tokio::test]
async fn blob_contract_deployment() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
));
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_size = std::fs::metadata(contract_binary)
.expect("contract file not found")
.len();
assert!(
contract_size > 150_000,
"the testnet size limit was around 100kB, we want a contract bigger than that to reflect prod (current: {contract_size}B)"
);
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::new(Some(2), None, None), None, None)
.await?;
let provider = wallets[0].provider().unwrap().clone();
let consensus_parameters = provider.consensus_parameters().await?;
let contract_max_size = consensus_parameters.contract_params().contract_max_size();
assert!(
contract_size > contract_max_size,
"this test should ideally be run with a contract bigger than the max contract size ({contract_max_size}B) so that we know deployment couldn't have happened without blobs"
);
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100_000)?
.deploy_if_not_exists(&wallets[0], TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallets[0].clone());
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn regular_contract_can_be_deployed() -> Result<()> {
// given
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
);
let contract_binary = "sway/contracts/contract_test/out/release/contract_test.bin";
// when
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
// then
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance
.methods()
.get_counter()
.call()
.await?
.value;
assert_eq!(response, 0);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_be_deployed_directly() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn unuploaded_loader_can_upload_blobs_separately_then_deploy() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?
.upload_blobs(&wallet, TxPolicies::default())
.await?;
let blob_ids = contract.blob_ids();
// if this were an example for the user we'd just call `deploy` on the contract above
// this way we are testing that the blobs were really deployed above, otherwise the following
// would fail
let contract_id = Contract::loader_from_blob_ids(
blob_ids.to_vec(),
contract.salt(),
contract.storage_slots().to_vec(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_blob_already_uploaded_not_an_issue() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?
.convert_to_loader(1024)?;
// this will upload blobs
contract
.clone()
.upload_blobs(&wallet, TxPolicies::default())
.await?;
// this will try to upload the blobs but skip upon encountering an error
let contract_id = contract
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_id = Contract::load_from(contract_binary, LoadConfiguration::default())?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.something()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 1001);
Ok(())
}
#[tokio::test]
async fn loader_storage_works_via_proxy() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
abigen!(
Contract(
name = "MyContract",
abi = "e2e/sway/contracts/huge_contract/out/release/huge_contract-abi.json"
),
Contract(
name = "MyProxy",
abi = "e2e/sway/contracts/proxy/out/release/proxy-abi.json"
)
);
let contract_binary = "sway/contracts/huge_contract/out/release/huge_contract.bin";
let contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let contract_storage_slots = contract.storage_slots().to_vec();
let contract_id = contract
.convert_to_loader(100)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_binary = "sway/contracts/proxy/out/release/proxy.bin";
let proxy_contract = Contract::load_from(contract_binary, LoadConfiguration::default())?;
let combined_storage_slots = [&contract_storage_slots, proxy_contract.storage_slots()].concat();
let proxy_id = proxy_contract
.with_storage_slots(combined_storage_slots)
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let proxy = MyProxy::new(proxy_id, wallet.clone());
proxy
.methods()
.set_target_contract(contract_id.clone())
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id.clone()])
.call()
.await?
.value;
assert_eq!(response, 42);
let _res = proxy
.methods()
.write_some_u64(36)
.with_contract_ids(&[contract_id.clone()])
.call()
.await?;
let response = proxy
.methods()
.read_some_u64()
.with_contract_ids(&[contract_id])
.call()
.await?
.value;
assert_eq!(response, 36);
Ok(())
}
Increasing the block height
You can use produce_blocks to help achieve an arbitrary block height; this is useful when you want to do any testing regarding transaction maturity.
Note: For the
produce_blocksAPI to work, it is imperative to havemanual_blocks_enabled = truein the config for the running node. See example below.
use std::{ops::Add, path::Path};
use chrono::{DateTime, Duration, TimeZone, Utc};
use fuel_asm::RegId;
use fuel_tx::Witness;
use fuels::{
accounts::{impersonated_account::ImpersonatedAccount, Account},
client::{PageDirection, PaginationRequest},
prelude::*,
tx::Receipt,
types::{
coin_type::CoinType,
message::Message,
transaction_builders::{BuildableTransaction, ScriptTransactionBuilder},
tx_status::TxStatus,
Bits256,
},
};
#[tokio::test]
async fn test_provider_launch_and_connect() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance_connected = MyContract::new(contract_id.clone(), wallet.clone());
let response = contract_instance_connected
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
wallet.set_provider(provider);
let contract_instance_launched = MyContract::new(contract_id, wallet);
let response = contract_instance_launched
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn test_network_error() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let mut wallet = WalletUnlocked::new_random(None);
let node_config = NodeConfig::default();
let chain_config = ChainConfig::default();
let state_config = StateConfig::default();
let service = FuelService::start(node_config, chain_config, state_config).await?;
let provider = Provider::connect(service.bound_address().to_string()).await?;
wallet.set_provider(provider);
// Simulate an unreachable node
service.stop().await.unwrap();
let response = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await;
assert!(matches!(response, Err(Error::Provider(_))));
Ok(())
}
#[tokio::test]
async fn test_input_message() -> Result<()> {
let compare_messages =
|messages_from_provider: Vec<Message>, used_messages: Vec<Message>| -> bool {
std::iter::zip(&used_messages, &messages_from_provider).all(|(a, b)| {
a.sender == b.sender
&& a.recipient == b.recipient
&& a.nonce == b.nonce
&& a.amount == b.amount
})
};
let mut wallet = WalletUnlocked::new_random(None);
// coin to pay transaction fee
let coins =
setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, DEFAULT_COIN_AMOUNT);
let messages = vec![setup_single_message(
&Bech32Address::default(),
wallet.address(),
DEFAULT_COIN_AMOUNT,
0.into(),
vec![1, 2],
)];
let provider = setup_test_provider(coins, messages.clone(), None, None).await?;
wallet.set_provider(provider);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let spendable_messages = wallet.get_messages().await?;
assert!(compare_messages(spendable_messages, messages));
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn test_input_message_pays_fee() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let messages = setup_single_message(
&Bech32Address {
hrp: "".to_string(),
hash: Default::default(),
},
wallet.address(),
DEFAULT_COIN_AMOUNT,
0.into(),
vec![],
);
let provider = setup_test_provider(vec![], vec![messages], None, None).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let base_asset_id = consensus_parameters.base_asset_id();
wallet.set_provider(provider);
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let balance = wallet.get_asset_balance(base_asset_id).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(balance, DEFAULT_COIN_AMOUNT - expected_fee);
Ok(())
}
#[tokio::test]
async fn can_increase_block_height() -> Result<()> {
// ANCHOR: use_produce_blocks_to_increase_block_height
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
assert_eq!(provider.latest_block_height().await?, 0u32);
provider.produce_blocks(3, None).await?;
assert_eq!(provider.latest_block_height().await?, 3u32);
// ANCHOR_END: use_produce_blocks_to_increase_block_height
Ok(())
}
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary), makes for
// flaky tests
#[cfg(not(feature = "fuel-core-lib"))]
#[tokio::test]
async fn can_set_custom_block_time() -> Result<()> {
// ANCHOR: use_produce_blocks_custom_time
let block_time = 20u32; // seconds
let config = NodeConfig {
// This is how you specify the time between blocks
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None)
.await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
assert_eq!(provider.latest_block_height().await?, 0u32);
let origin_block_time = provider.latest_block_time().await?.unwrap();
let blocks_to_produce = 3;
provider.produce_blocks(blocks_to_produce, None).await?;
assert_eq!(provider.latest_block_height().await?, blocks_to_produce);
let expected_latest_block_time = origin_block_time
.checked_add_signed(Duration::try_seconds((blocks_to_produce * block_time) as i64).unwrap())
.unwrap();
assert_eq!(
provider.latest_block_time().await?.unwrap(),
expected_latest_block_time
);
// ANCHOR_END: use_produce_blocks_custom_time
let req = PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
};
let blocks: Vec<fuels::types::block::Block> = provider.get_blocks(req).await?.results;
assert_eq!(blocks[1].header.time.unwrap().timestamp(), 20);
assert_eq!(blocks[2].header.time.unwrap().timestamp(), 40);
assert_eq!(blocks[3].header.time.unwrap().timestamp(), 60);
Ok(())
}
#[tokio::test]
async fn can_retrieve_latest_block_time() -> Result<()> {
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let since_epoch = 1676039910;
let latest_timestamp = Utc.timestamp_opt(since_epoch, 0).unwrap();
provider.produce_blocks(1, Some(latest_timestamp)).await?;
assert_eq!(
provider.latest_block_time().await?.unwrap(),
latest_timestamp
);
Ok(())
}
#[tokio::test]
async fn contract_deployment_respects_maturity() -> Result<()> {
abigen!(Contract(name="MyContract", abi="e2e/sway/contracts/transaction_block_height/out/release/transaction_block_height-abi.json"));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
let deploy_w_maturity = |maturity| {
Contract::load_from(
"sway/contracts/transaction_block_height/out/release/transaction_block_height.bin",
LoadConfiguration::default(),
)
.map(|loaded_contract| {
loaded_contract
.deploy_if_not_exists(wallet, TxPolicies::default().with_maturity(maturity))
})
};
let err = deploy_w_maturity(1)?.await.expect_err(
"should not deploy contract since block height `0` is less than the requested maturity `1`",
);
let Error::Provider(s) = err else {
panic!("expected `Validation`, got: `{err}`");
};
assert!(s.contains("TransactionMaturity"));
provider.produce_blocks(1, None).await?;
deploy_w_maturity(1)?
.await
.expect("Should deploy contract since maturity `1` is <= than the block height `1`");
Ok(())
}
#[tokio::test]
async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// The gas used by the script to call a contract and forward remaining gas limit.
let gas_used_by_script = 247;
let gas_limit = 225_883;
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.call()
.await?;
let gas_forwarded = response
.receipts
.iter()
.find(|r| matches!(r, Receipt::Call { .. }))
.unwrap()
.gas()
.unwrap();
assert_eq!(gas_limit, gas_forwarded + gas_used_by_script);
Ok(())
}
#[tokio::test]
async fn test_amount_and_asset_forwarding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TokenContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TokenContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
let contract_methods = contract_instance.methods();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let mut balance_response = contract_methods
.get_balance(contract_id, asset_id)
.call()
.await?;
assert_eq!(balance_response.value, 0);
contract_methods.mint_coins(5_000_000).call().await?;
balance_response = contract_methods
.get_balance(contract_id, asset_id)
.call()
.await?;
assert_eq!(balance_response.value, 5_000_000);
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params)?
.call()
.await?;
assert_eq!(response.value, 1_000_000);
let call_response = response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::Call { .. }));
assert!(call_response.is_some());
assert_eq!(call_response.unwrap().amount().unwrap(), 1_000_000);
assert_eq!(
call_response.unwrap().asset_id().unwrap(),
&AssetId::zeroed()
);
let address = wallet.address();
// withdraw some tokens to wallet
contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let asset_id = AssetId::from(*contract_id.hash());
let call_params = CallParameters::default()
.with_amount(0)
.with_asset_id(asset_id);
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params)?
.call()
.await?;
assert_eq!(response.value, 0);
let call_response = response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::Call { .. }));
assert!(call_response.is_some());
assert_eq!(call_response.unwrap().amount().unwrap(), 0);
assert_eq!(
call_response.unwrap().asset_id().unwrap(),
&AssetId::from(*contract_id.hash())
);
Ok(())
}
#[tokio::test]
async fn test_gas_errors() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let number_of_coins = 1;
let amount_per_coin = 1_000_000;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Test running out of gas. Gas price as `None` will be 0.
let gas_limit = 100;
let contract_instance_call = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit));
// Test that the call will use more gas than the gas limit
let gas_used = contract_instance_call
.estimate_transaction_cost(None, None)
.await?
.gas_used;
assert!(gas_used > gas_limit);
let response = contract_instance_call
.call()
.await
.expect_err("should error");
let expected = "transaction reverted: OutOfGas";
assert!(response.to_string().starts_with(expected));
// Test for insufficient base asset amount to pay for the transaction fee
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.with_tx_policies(TxPolicies::default().with_tip(100_000_000_000))
.call()
.await
.expect_err("should error");
let expected = "Response errors; Validity(InsufficientFeeAmount";
assert!(response.to_string().contains(expected));
Ok(())
}
#[tokio::test]
async fn test_call_param_gas_errors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Transaction gas_limit is sufficient, call gas_forwarded is too small
let contract_methods = contract_instance.methods();
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(446000))
.call_params(CallParameters::default().with_gas_forwarded(1))?
.call()
.await
.expect_err("should error");
let expected = "transaction reverted: OutOfGas";
assert!(response.to_string().starts_with(expected));
// Call params gas_forwarded exceeds transaction limit
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(1))
.call_params(CallParameters::default().with_gas_forwarded(1_000))?
.call()
.await
.expect_err("should error");
assert!(response.to_string().contains(expected));
Ok(())
}
#[tokio::test]
async fn test_get_gas_used() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_used = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.gas_used;
assert!(gas_used > 0);
Ok(())
}
#[tokio::test]
async fn test_parse_block_time() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let asset_id = AssetId::zeroed();
let coins = setup_single_asset_coins(wallet.address(), asset_id, 1, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
let tx_policies = TxPolicies::default().with_script_gas_limit(2000);
let wallet_2 = WalletUnlocked::new_random(None).lock();
let (tx_id, _) = wallet
.transfer(wallet_2.address(), 100, asset_id, tx_policies)
.await?;
let tx_response = wallet
.try_provider()?
.get_transaction_by_id(&tx_id)
.await?
.unwrap();
assert!(tx_response.time.is_some());
let block = wallet
.try_provider()?
.block_by_height(tx_response.block_height.unwrap())
.await?
.unwrap();
assert!(block.header.time.is_some());
Ok(())
}
#[tokio::test]
async fn test_get_spendable_with_exclusion() -> Result<()> {
let coin_amount_1 = 1000;
let coin_amount_2 = 500;
let mut wallet = WalletUnlocked::new_random(None);
let address = wallet.address();
let coins = [coin_amount_1, coin_amount_2]
.into_iter()
.flat_map(|amount| setup_single_asset_coins(address, AssetId::zeroed(), 1, amount))
.collect::<Vec<_>>();
let message_amount = 200;
let message = given_a_message(address.clone(), message_amount);
let coin_1_utxo_id = coins[0].utxo_id;
let coin_2_utxo_id = coins[1].utxo_id;
let message_nonce = message.nonce;
let provider = setup_test_provider(coins, vec![message], None, None).await?;
wallet.set_provider(provider.clone());
let requested_amount = coin_amount_1 + coin_amount_2 + message_amount;
let consensus_parameters = provider.consensus_parameters().await?;
{
let resources = wallet
.get_spendable_resources(
*consensus_parameters.base_asset_id(),
requested_amount,
None,
)
.await
.unwrap();
assert_eq!(resources.len(), 3);
}
{
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: coin_amount_1,
excluded_utxos: vec![coin_2_utxo_id],
excluded_message_nonces: vec![message_nonce],
..Default::default()
};
let resources = provider.get_spendable_resources(filter).await.unwrap();
match resources.as_slice() {
[CoinType::Coin(coin)] => {
assert_eq!(coin.utxo_id, coin_1_utxo_id);
}
_ => {
panic!("This shouldn't happen!")
}
}
}
Ok(())
}
fn given_a_message(address: Bech32Address, message_amount: u64) -> Message {
setup_single_message(
&Bech32Address::default(),
&address,
message_amount,
0.into(),
vec![],
)
}
fn convert_to_datetime(timestamp: u64) -> DateTime<Utc> {
let unix = tai64::Tai64(timestamp).to_unix();
DateTime::from_timestamp(unix, 0).unwrap()
}
/// This test is here in addition to `can_set_custom_block_time` because even though this test
/// passed, the Sway `timestamp` function didn't take into account the block time change. This
/// was fixed and this test is here to demonstrate the fix.
#[tokio::test]
async fn test_sway_timestamp() -> Result<()> {
let block_time = 1u32; // seconds
let provider_config = NodeConfig {
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(1), Some(100)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.try_provider()?;
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/block_timestamp"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let origin_timestamp = provider.latest_block_time().await?.unwrap();
let methods = contract_instance.methods();
let response = methods.return_timestamp().call().await?;
let mut expected_datetime =
origin_timestamp.add(Duration::try_seconds(block_time as i64).unwrap());
assert_eq!(convert_to_datetime(response.value), expected_datetime);
let blocks_to_produce = 600;
provider.produce_blocks(blocks_to_produce, None).await?;
let response = methods.return_timestamp().call().await?;
// `produce_blocks` call
expected_datetime = expected_datetime
.add(Duration::try_seconds((block_time * blocks_to_produce) as i64).unwrap());
// method call
expected_datetime = expected_datetime.add(Duration::try_seconds(block_time as i64).unwrap());
assert_eq!(convert_to_datetime(response.value), expected_datetime);
assert_eq!(
provider.latest_block_time().await?.unwrap(),
expected_datetime
);
Ok(())
}
#[cfg(feature = "coin-cache")]
async fn create_transfer(
wallet: &WalletUnlocked,
amount: u64,
to: &Bech32Address,
) -> Result<ScriptTransaction> {
let asset_id = AssetId::zeroed();
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(to, asset_id, amount);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, amount).await?;
tb.build(wallet.try_provider()?).await
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn transactions_with_the_same_utxo() -> Result<()> {
use fuels::types::errors::transaction;
let wallet_1 = launch_provider_and_get_wallet().await?;
let provider = wallet_1.provider().unwrap();
let wallet_2 = WalletUnlocked::new_random(Some(provider.clone()));
let tx_1 = create_transfer(&wallet_1, 100, wallet_2.address()).await?;
let tx_2 = create_transfer(&wallet_1, 101, wallet_2.address()).await?;
let _tx_id = provider.send_transaction(tx_1).await?;
let res = provider.send_transaction(tx_2).await;
let err = res.expect_err("is error");
assert!(matches!(
err,
Error::Transaction(transaction::Reason::Validation(..))
));
assert!(err
.to_string()
.contains("was submitted recently in a transaction "));
Ok(())
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn test_caching() -> Result<()> {
let amount = 1000;
let num_coins = 10;
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(num_coins), Some(amount)),
Some(NodeConfig::default()),
None,
)
.await?;
let wallet_1 = wallets.pop().unwrap();
let provider = wallet_1.provider().unwrap();
let wallet_2 = WalletUnlocked::new_random(Some(provider.clone()));
// Consecutively send transfer txs. Without caching, the txs will
// end up trying to use the same input coins because 'get_spendable_coins()'
// won't filter out recently used coins.
let mut tx_ids = vec![];
for _ in 0..10 {
let tx = create_transfer(&wallet_1, 100, wallet_2.address()).await?;
let tx_id = provider.send_transaction(tx).await?;
tx_ids.push(tx_id);
}
provider.produce_blocks(10, None).await?;
// Confirm all txs are settled
for tx_id in tx_ids {
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
}
// Verify the transfers were successful
assert_eq!(wallet_2.get_asset_balance(&AssetId::zeroed()).await?, 1000);
Ok(())
}
#[cfg(feature = "coin-cache")]
async fn create_revert_tx(wallet: &WalletUnlocked) -> Result<ScriptTransaction> {
let script = std::fs::read("sway/scripts/reverting/out/release/reverting.bin")?;
let amount = 1;
let asset_id = AssetId::zeroed();
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(&Bech32Address::default(), asset_id, amount);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default())
.with_script(script);
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, amount).await?;
tb.build(wallet.try_provider()?).await
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn test_cache_invalidation_on_await() -> Result<()> {
let block_time = 1u32;
let provider_config = NodeConfig {
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
// create wallet with 1 coin so that the cache prevents further
// spending unless the coin is invalidated from the cache
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(1), Some(100)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.provider().unwrap();
let tx = create_revert_tx(&wallet).await?;
// Pause time so that the cache doesn't invalidate items based on TTL
tokio::time::pause();
// tx inputs should be cached and then invalidated due to the tx failing
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
assert!(matches!(tx_status, TxStatus::Revert { .. }));
let consensus_parameters = provider.consensus_parameters().await?;
let coins = wallet
.get_spendable_resources(*consensus_parameters.base_asset_id(), 1, None)
.await?;
assert_eq!(coins.len(), 1);
Ok(())
}
#[tokio::test]
async fn can_fetch_mint_transactions() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let transactions = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results;
// TODO: remove once (fuels-rs#1093)[https://github.com/FuelLabs/fuels-rs/issues/1093] is in
// until then the type is explicitly mentioned to check that we're reexporting it through fuels
let _: ::fuels::types::transaction::MintTransaction = transactions
.into_iter()
.find_map(|tx| match tx.transaction {
TransactionType::Mint(tx) => Some(tx),
_ => None,
})
.expect("Should have had at least one mint transaction");
Ok(())
}
#[tokio::test]
async fn test_build_with_provider() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?;
let receiver = WalletUnlocked::new_random(Some(provider.clone()));
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
receiver.address(),
*consensus_parameters.base_asset_id(),
100,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let receiver_balance = receiver
.get_asset_balance(consensus_parameters.base_asset_id())
.await?;
assert_eq!(receiver_balance, 100);
Ok(())
}
#[tokio::test]
async fn can_produce_blocks_with_trig_never() -> Result<()> {
let config = NodeConfig {
block_production: Trigger::Never,
..NodeConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None)
.await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
&Bech32Address::default(),
*consensus_parameters.base_asset_id(),
100,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
provider.send_transaction(tx).await?;
provider.produce_blocks(1, None).await?;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
Ok(())
}
#[tokio::test]
async fn can_upload_executor_and_trigger_upgrade() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
// Need more coins to avoid "not enough coins to fit the target"
let num_coins = 100;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
num_coins,
DEFAULT_COIN_AMOUNT,
);
let mut chain_config = ChainConfig::local_testnet();
chain_config
.consensus_parameters
.set_privileged_address(wallet.address().into());
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
// This is downloaded over in `build.rs`
let executor = std::fs::read(Path::new(env!("OUT_DIR")).join("fuel-core-wasm-executor.wasm"))?;
let subsection_size = 65536;
let subsections = UploadSubsection::split_bytecode(&executor, subsection_size).unwrap();
let root = subsections[0].root;
for subsection in subsections {
let mut builder =
UploadTransactionBuilder::prepare_subsection_upload(subsection, TxPolicies::default());
wallet.add_witnesses(&mut builder)?;
wallet.adjust_for_fee(&mut builder, 0).await?;
let tx = builder.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
}
let mut builder =
UpgradeTransactionBuilder::prepare_state_transition_upgrade(root, TxPolicies::default());
wallet.add_witnesses(&mut builder)?;
wallet.adjust_for_fee(&mut builder, 0).await?;
let tx = builder.build(provider.clone()).await?;
provider.send_transaction(tx).await?;
Ok(())
}
#[tokio::test]
async fn tx_respects_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let tip = 22;
let witness_limit = 1000;
let maturity = 4;
let max_fee = 10_000;
let script_gas_limit = 3000;
let tx_policies = TxPolicies::new(
Some(tip),
Some(witness_limit),
Some(maturity),
Some(max_fee),
Some(script_gas_limit),
);
// advance the block height to ensure the maturity is respected
let provider = wallet.try_provider()?;
provider.produce_blocks(4, None).await?;
// trigger a transaction that contains script code to verify
// that policies precede estimated values
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
let tx_response = provider
.get_transaction_by_id(&response.tx_id.unwrap())
.await?
.expect("tx should exist");
let script = match tx_response.transaction {
TransactionType::Script(tx) => tx,
_ => panic!("expected script transaction"),
};
assert_eq!(script.maturity(), maturity as u32);
assert_eq!(script.tip().unwrap(), tip);
assert_eq!(script.witness_limit().unwrap(), witness_limit);
assert_eq!(script.max_fee().unwrap(), max_fee);
assert_eq!(script.gas_limit(), script_gas_limit);
Ok(())
}
#[tokio::test]
#[ignore] //TODO: https://github.com/FuelLabs/fuels-rs/issues/1581
async fn can_setup_static_gas_price() -> Result<()> {
let expected_gas_price = 474;
let node_config = NodeConfig {
starting_gas_price: expected_gas_price,
..Default::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
let gas_price = provider.estimate_gas_price(0).await?.gas_price;
let da_cost = 1000;
assert_eq!(gas_price, da_cost + expected_gas_price);
Ok(())
}
#[tokio::test]
async fn tx_with_witness_data() -> Result<()> {
use fuel_asm::{op, GTFArgs};
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?;
let receiver = WalletUnlocked::new_random(Some(provider.clone()));
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 10000, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
receiver.address(),
*consensus_parameters.base_asset_id(),
1,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
// we test that the witness data wasn't tempered with during the build (gas estimation) process
// if the witness data is tempered with, the estimation will be off and the transaction
// will error out with `OutOfGas`
let script: Vec<u8> = vec![
// load witness data into register 0x10
op::gtf(0x10, 0x00, GTFArgs::WitnessData.into()),
op::lw(0x10, 0x10, 0x00),
// load expected value into register 0x11
op::movi(0x11, 0x0f),
// load the offset of the revert instruction into register 0x12
op::movi(0x12, 0x08),
// compare the two values and jump to the revert instruction if they are not equal
op::jne(0x10, 0x11, 0x12),
// do some expensive operation so gas estimation is higher if comparison passes
op::gtf(0x13, 0x01, GTFArgs::WitnessData.into()),
op::gtf(0x14, 0x01, GTFArgs::WitnessDataLength.into()),
op::aloc(0x14),
op::eck1(RegId::HP, 0x13, 0x13),
// return the witness data
op::ret(0x10),
op::rvrt(RegId::ZERO),
]
.into_iter()
.collect();
tb.script = script;
let expected_data = 15u64;
let witness = Witness::from(expected_data.to_be_bytes().to_vec());
tb.witnesses_mut().push(witness);
let tx = tb
.with_tx_policies(TxPolicies::default().with_witness_limit(1000))
.build(provider)
.await?;
let status = provider.send_transaction_and_await_commit(tx).await?;
match status {
TxStatus::Success { receipts } => {
let ret: u64 = receipts
.into_iter()
.find_map(|receipt| match receipt {
Receipt::Return { val, .. } => Some(val),
_ => None,
})
.expect("should have return value");
assert_eq!(ret, expected_data);
}
_ => panic!("expected success status"),
}
Ok(())
}
#[tokio::test]
async fn contract_call_with_impersonation() -> Result<()> {
let provider_config = NodeConfig {
utxo_validation: false,
..NodeConfig::default()
};
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(10), Some(1000)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.try_provider()?;
let impersonator = ImpersonatedAccount::new(wallet.address().clone(), Some(provider.clone()));
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, impersonator.clone());
// The gas used by the script to call a contract and forward remaining gas limit.
contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
Ok(())
}
#[tokio::test]
async fn is_account_query_test() -> Result<()> {
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let blob = Blob::new(vec![1; 100]);
let blob_id = blob.id();
let is_account = provider.is_user_account(blob_id).await?;
assert!(is_account);
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(provider.clone()).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let is_account = provider.is_user_account(blob_id).await?;
assert!(!is_account);
}
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let contract = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?;
let contract_id = contract.contract_id();
let is_account = provider.is_user_account(*contract_id).await?;
assert!(is_account);
contract.deploy(&wallet, TxPolicies::default()).await?;
let is_account = provider.is_user_account(*contract_id).await?;
assert!(!is_account);
}
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let mut tb = ScriptTransactionBuilder::default();
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(provider.clone()).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let is_account = provider.is_user_account(tx_id).await?;
assert!(is_account);
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let is_account = provider.is_user_account(tx_id).await?;
assert!(!is_account);
}
Ok(())
}
You can also set a custom block time as the second, optional argument. Here is an example:
use std::{ops::Add, path::Path};
use chrono::{DateTime, Duration, TimeZone, Utc};
use fuel_asm::RegId;
use fuel_tx::Witness;
use fuels::{
accounts::{impersonated_account::ImpersonatedAccount, Account},
client::{PageDirection, PaginationRequest},
prelude::*,
tx::Receipt,
types::{
coin_type::CoinType,
message::Message,
transaction_builders::{BuildableTransaction, ScriptTransactionBuilder},
tx_status::TxStatus,
Bits256,
},
};
#[tokio::test]
async fn test_provider_launch_and_connect() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let mut wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance_connected = MyContract::new(contract_id.clone(), wallet.clone());
let response = contract_instance_connected
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
wallet.set_provider(provider);
let contract_instance_launched = MyContract::new(contract_id, wallet);
let response = contract_instance_launched
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
Ok(())
}
#[tokio::test]
async fn test_network_error() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let mut wallet = WalletUnlocked::new_random(None);
let node_config = NodeConfig::default();
let chain_config = ChainConfig::default();
let state_config = StateConfig::default();
let service = FuelService::start(node_config, chain_config, state_config).await?;
let provider = Provider::connect(service.bound_address().to_string()).await?;
wallet.set_provider(provider);
// Simulate an unreachable node
service.stop().await.unwrap();
let response = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await;
assert!(matches!(response, Err(Error::Provider(_))));
Ok(())
}
#[tokio::test]
async fn test_input_message() -> Result<()> {
let compare_messages =
|messages_from_provider: Vec<Message>, used_messages: Vec<Message>| -> bool {
std::iter::zip(&used_messages, &messages_from_provider).all(|(a, b)| {
a.sender == b.sender
&& a.recipient == b.recipient
&& a.nonce == b.nonce
&& a.amount == b.amount
})
};
let mut wallet = WalletUnlocked::new_random(None);
// coin to pay transaction fee
let coins =
setup_single_asset_coins(wallet.address(), AssetId::zeroed(), 1, DEFAULT_COIN_AMOUNT);
let messages = vec![setup_single_message(
&Bech32Address::default(),
wallet.address(),
DEFAULT_COIN_AMOUNT,
0.into(),
vec![1, 2],
)];
let provider = setup_test_provider(coins, messages.clone(), None, None).await?;
wallet.set_provider(provider);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let spendable_messages = wallet.get_messages().await?;
assert!(compare_messages(spendable_messages, messages));
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
async fn test_input_message_pays_fee() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let messages = setup_single_message(
&Bech32Address {
hrp: "".to_string(),
hash: Default::default(),
},
wallet.address(),
DEFAULT_COIN_AMOUNT,
0.into(),
vec![],
);
let provider = setup_test_provider(vec![], vec![messages], None, None).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let base_asset_id = consensus_parameters.base_asset_id();
wallet.set_provider(provider);
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, wallet.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let balance = wallet.get_asset_balance(base_asset_id).await?;
// TODO: https://github.com/FuelLabs/fuels-rs/issues/1394
let expected_fee = 2;
assert_eq!(balance, DEFAULT_COIN_AMOUNT - expected_fee);
Ok(())
}
#[tokio::test]
async fn can_increase_block_height() -> Result<()> {
// ANCHOR: use_produce_blocks_to_increase_block_height
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
assert_eq!(provider.latest_block_height().await?, 0u32);
provider.produce_blocks(3, None).await?;
assert_eq!(provider.latest_block_height().await?, 3u32);
// ANCHOR_END: use_produce_blocks_to_increase_block_height
Ok(())
}
// debug builds are slower (20x for `fuel-core-lib`, 4x for a release-fuel-core-binary), makes for
// flaky tests
#[cfg(not(feature = "fuel-core-lib"))]
#[tokio::test]
async fn can_set_custom_block_time() -> Result<()> {
// ANCHOR: use_produce_blocks_custom_time
let block_time = 20u32; // seconds
let config = NodeConfig {
// This is how you specify the time between blocks
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None)
.await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
assert_eq!(provider.latest_block_height().await?, 0u32);
let origin_block_time = provider.latest_block_time().await?.unwrap();
let blocks_to_produce = 3;
provider.produce_blocks(blocks_to_produce, None).await?;
assert_eq!(provider.latest_block_height().await?, blocks_to_produce);
let expected_latest_block_time = origin_block_time
.checked_add_signed(Duration::try_seconds((blocks_to_produce * block_time) as i64).unwrap())
.unwrap();
assert_eq!(
provider.latest_block_time().await?.unwrap(),
expected_latest_block_time
);
// ANCHOR_END: use_produce_blocks_custom_time
let req = PaginationRequest {
cursor: None,
results: 10,
direction: PageDirection::Forward,
};
let blocks: Vec<fuels::types::block::Block> = provider.get_blocks(req).await?.results;
assert_eq!(blocks[1].header.time.unwrap().timestamp(), 20);
assert_eq!(blocks[2].header.time.unwrap().timestamp(), 40);
assert_eq!(blocks[3].header.time.unwrap().timestamp(), 60);
Ok(())
}
#[tokio::test]
async fn can_retrieve_latest_block_time() -> Result<()> {
let provider = setup_test_provider(vec![], vec![], None, None).await?;
let since_epoch = 1676039910;
let latest_timestamp = Utc.timestamp_opt(since_epoch, 0).unwrap();
provider.produce_blocks(1, Some(latest_timestamp)).await?;
assert_eq!(
provider.latest_block_time().await?.unwrap(),
latest_timestamp
);
Ok(())
}
#[tokio::test]
async fn contract_deployment_respects_maturity() -> Result<()> {
abigen!(Contract(name="MyContract", abi="e2e/sway/contracts/transaction_block_height/out/release/transaction_block_height-abi.json"));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
let deploy_w_maturity = |maturity| {
Contract::load_from(
"sway/contracts/transaction_block_height/out/release/transaction_block_height.bin",
LoadConfiguration::default(),
)
.map(|loaded_contract| {
loaded_contract
.deploy_if_not_exists(wallet, TxPolicies::default().with_maturity(maturity))
})
};
let err = deploy_w_maturity(1)?.await.expect_err(
"should not deploy contract since block height `0` is less than the requested maturity `1`",
);
let Error::Provider(s) = err else {
panic!("expected `Validation`, got: `{err}`");
};
assert!(s.contains("TransactionMaturity"));
provider.produce_blocks(1, None).await?;
deploy_w_maturity(1)?
.await
.expect("Should deploy contract since maturity `1` is <= than the block height `1`");
Ok(())
}
#[tokio::test]
async fn test_gas_forwarded_defaults_to_tx_limit() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// The gas used by the script to call a contract and forward remaining gas limit.
let gas_used_by_script = 247;
let gas_limit = 225_883;
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit))
.call()
.await?;
let gas_forwarded = response
.receipts
.iter()
.find(|r| matches!(r, Receipt::Call { .. }))
.unwrap()
.gas()
.unwrap();
assert_eq!(gas_limit, gas_forwarded + gas_used_by_script);
Ok(())
}
#[tokio::test]
async fn test_amount_and_asset_forwarding() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TokenContract",
project = "e2e/sway/contracts/token_ops"
)),
Deploy(
name = "contract_instance",
contract = "TokenContract",
wallet = "wallet",
random_salt = false,
),
);
let contract_id = contract_instance.contract_id();
let contract_methods = contract_instance.methods();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
let mut balance_response = contract_methods
.get_balance(contract_id, asset_id)
.call()
.await?;
assert_eq!(balance_response.value, 0);
contract_methods.mint_coins(5_000_000).call().await?;
balance_response = contract_methods
.get_balance(contract_id, asset_id)
.call()
.await?;
assert_eq!(balance_response.value, 5_000_000);
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params)?
.call()
.await?;
assert_eq!(response.value, 1_000_000);
let call_response = response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::Call { .. }));
assert!(call_response.is_some());
assert_eq!(call_response.unwrap().amount().unwrap(), 1_000_000);
assert_eq!(
call_response.unwrap().asset_id().unwrap(),
&AssetId::zeroed()
);
let address = wallet.address();
// withdraw some tokens to wallet
contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let asset_id = AssetId::from(*contract_id.hash());
let call_params = CallParameters::default()
.with_amount(0)
.with_asset_id(asset_id);
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let response = contract_methods
.get_msg_amount()
.with_tx_policies(tx_policies)
.call_params(call_params)?
.call()
.await?;
assert_eq!(response.value, 0);
let call_response = response
.receipts
.iter()
.find(|&r| matches!(r, Receipt::Call { .. }));
assert!(call_response.is_some());
assert_eq!(call_response.unwrap().amount().unwrap(), 0);
assert_eq!(
call_response.unwrap().asset_id().unwrap(),
&AssetId::from(*contract_id.hash())
);
Ok(())
}
#[tokio::test]
async fn test_gas_errors() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let number_of_coins = 1;
let amount_per_coin = 1_000_000;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
number_of_coins,
amount_per_coin,
);
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Test running out of gas. Gas price as `None` will be 0.
let gas_limit = 100;
let contract_instance_call = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.with_tx_policies(TxPolicies::default().with_script_gas_limit(gas_limit));
// Test that the call will use more gas than the gas limit
let gas_used = contract_instance_call
.estimate_transaction_cost(None, None)
.await?
.gas_used;
assert!(gas_used > gas_limit);
let response = contract_instance_call
.call()
.await
.expect_err("should error");
let expected = "transaction reverted: OutOfGas";
assert!(response.to_string().starts_with(expected));
// Test for insufficient base asset amount to pay for the transaction fee
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.with_tx_policies(TxPolicies::default().with_tip(100_000_000_000))
.call()
.await
.expect_err("should error");
let expected = "Response errors; Validity(InsufficientFeeAmount";
assert!(response.to_string().contains(expected));
Ok(())
}
#[tokio::test]
async fn test_call_param_gas_errors() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
// Transaction gas_limit is sufficient, call gas_forwarded is too small
let contract_methods = contract_instance.methods();
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(446000))
.call_params(CallParameters::default().with_gas_forwarded(1))?
.call()
.await
.expect_err("should error");
let expected = "transaction reverted: OutOfGas";
assert!(response.to_string().starts_with(expected));
// Call params gas_forwarded exceeds transaction limit
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default().with_script_gas_limit(1))
.call_params(CallParameters::default().with_gas_forwarded(1_000))?
.call()
.await
.expect_err("should error");
assert!(response.to_string().contains(expected));
Ok(())
}
#[tokio::test]
async fn test_get_gas_used() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let gas_used = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.gas_used;
assert!(gas_used > 0);
Ok(())
}
#[tokio::test]
async fn test_parse_block_time() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
let asset_id = AssetId::zeroed();
let coins = setup_single_asset_coins(wallet.address(), asset_id, 1, DEFAULT_COIN_AMOUNT);
let provider = setup_test_provider(coins.clone(), vec![], None, None).await?;
wallet.set_provider(provider);
let tx_policies = TxPolicies::default().with_script_gas_limit(2000);
let wallet_2 = WalletUnlocked::new_random(None).lock();
let (tx_id, _) = wallet
.transfer(wallet_2.address(), 100, asset_id, tx_policies)
.await?;
let tx_response = wallet
.try_provider()?
.get_transaction_by_id(&tx_id)
.await?
.unwrap();
assert!(tx_response.time.is_some());
let block = wallet
.try_provider()?
.block_by_height(tx_response.block_height.unwrap())
.await?
.unwrap();
assert!(block.header.time.is_some());
Ok(())
}
#[tokio::test]
async fn test_get_spendable_with_exclusion() -> Result<()> {
let coin_amount_1 = 1000;
let coin_amount_2 = 500;
let mut wallet = WalletUnlocked::new_random(None);
let address = wallet.address();
let coins = [coin_amount_1, coin_amount_2]
.into_iter()
.flat_map(|amount| setup_single_asset_coins(address, AssetId::zeroed(), 1, amount))
.collect::<Vec<_>>();
let message_amount = 200;
let message = given_a_message(address.clone(), message_amount);
let coin_1_utxo_id = coins[0].utxo_id;
let coin_2_utxo_id = coins[1].utxo_id;
let message_nonce = message.nonce;
let provider = setup_test_provider(coins, vec![message], None, None).await?;
wallet.set_provider(provider.clone());
let requested_amount = coin_amount_1 + coin_amount_2 + message_amount;
let consensus_parameters = provider.consensus_parameters().await?;
{
let resources = wallet
.get_spendable_resources(
*consensus_parameters.base_asset_id(),
requested_amount,
None,
)
.await
.unwrap();
assert_eq!(resources.len(), 3);
}
{
let filter = ResourceFilter {
from: wallet.address().clone(),
amount: coin_amount_1,
excluded_utxos: vec![coin_2_utxo_id],
excluded_message_nonces: vec![message_nonce],
..Default::default()
};
let resources = provider.get_spendable_resources(filter).await.unwrap();
match resources.as_slice() {
[CoinType::Coin(coin)] => {
assert_eq!(coin.utxo_id, coin_1_utxo_id);
}
_ => {
panic!("This shouldn't happen!")
}
}
}
Ok(())
}
fn given_a_message(address: Bech32Address, message_amount: u64) -> Message {
setup_single_message(
&Bech32Address::default(),
&address,
message_amount,
0.into(),
vec![],
)
}
fn convert_to_datetime(timestamp: u64) -> DateTime<Utc> {
let unix = tai64::Tai64(timestamp).to_unix();
DateTime::from_timestamp(unix, 0).unwrap()
}
/// This test is here in addition to `can_set_custom_block_time` because even though this test
/// passed, the Sway `timestamp` function didn't take into account the block time change. This
/// was fixed and this test is here to demonstrate the fix.
#[tokio::test]
async fn test_sway_timestamp() -> Result<()> {
let block_time = 1u32; // seconds
let provider_config = NodeConfig {
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(1), Some(100)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.try_provider()?;
setup_program_test!(
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/block_timestamp"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let origin_timestamp = provider.latest_block_time().await?.unwrap();
let methods = contract_instance.methods();
let response = methods.return_timestamp().call().await?;
let mut expected_datetime =
origin_timestamp.add(Duration::try_seconds(block_time as i64).unwrap());
assert_eq!(convert_to_datetime(response.value), expected_datetime);
let blocks_to_produce = 600;
provider.produce_blocks(blocks_to_produce, None).await?;
let response = methods.return_timestamp().call().await?;
// `produce_blocks` call
expected_datetime = expected_datetime
.add(Duration::try_seconds((block_time * blocks_to_produce) as i64).unwrap());
// method call
expected_datetime = expected_datetime.add(Duration::try_seconds(block_time as i64).unwrap());
assert_eq!(convert_to_datetime(response.value), expected_datetime);
assert_eq!(
provider.latest_block_time().await?.unwrap(),
expected_datetime
);
Ok(())
}
#[cfg(feature = "coin-cache")]
async fn create_transfer(
wallet: &WalletUnlocked,
amount: u64,
to: &Bech32Address,
) -> Result<ScriptTransaction> {
let asset_id = AssetId::zeroed();
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(to, asset_id, amount);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, amount).await?;
tb.build(wallet.try_provider()?).await
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn transactions_with_the_same_utxo() -> Result<()> {
use fuels::types::errors::transaction;
let wallet_1 = launch_provider_and_get_wallet().await?;
let provider = wallet_1.provider().unwrap();
let wallet_2 = WalletUnlocked::new_random(Some(provider.clone()));
let tx_1 = create_transfer(&wallet_1, 100, wallet_2.address()).await?;
let tx_2 = create_transfer(&wallet_1, 101, wallet_2.address()).await?;
let _tx_id = provider.send_transaction(tx_1).await?;
let res = provider.send_transaction(tx_2).await;
let err = res.expect_err("is error");
assert!(matches!(
err,
Error::Transaction(transaction::Reason::Validation(..))
));
assert!(err
.to_string()
.contains("was submitted recently in a transaction "));
Ok(())
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn test_caching() -> Result<()> {
let amount = 1000;
let num_coins = 10;
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(num_coins), Some(amount)),
Some(NodeConfig::default()),
None,
)
.await?;
let wallet_1 = wallets.pop().unwrap();
let provider = wallet_1.provider().unwrap();
let wallet_2 = WalletUnlocked::new_random(Some(provider.clone()));
// Consecutively send transfer txs. Without caching, the txs will
// end up trying to use the same input coins because 'get_spendable_coins()'
// won't filter out recently used coins.
let mut tx_ids = vec![];
for _ in 0..10 {
let tx = create_transfer(&wallet_1, 100, wallet_2.address()).await?;
let tx_id = provider.send_transaction(tx).await?;
tx_ids.push(tx_id);
}
provider.produce_blocks(10, None).await?;
// Confirm all txs are settled
for tx_id in tx_ids {
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
}
// Verify the transfers were successful
assert_eq!(wallet_2.get_asset_balance(&AssetId::zeroed()).await?, 1000);
Ok(())
}
#[cfg(feature = "coin-cache")]
async fn create_revert_tx(wallet: &WalletUnlocked) -> Result<ScriptTransaction> {
let script = std::fs::read("sway/scripts/reverting/out/release/reverting.bin")?;
let amount = 1;
let asset_id = AssetId::zeroed();
let inputs = wallet
.get_asset_inputs_for_amount(asset_id, amount, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(&Bech32Address::default(), asset_id, amount);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default())
.with_script(script);
tb.add_signer(wallet.clone())?;
wallet.adjust_for_fee(&mut tb, amount).await?;
tb.build(wallet.try_provider()?).await
}
#[cfg(feature = "coin-cache")]
#[tokio::test]
async fn test_cache_invalidation_on_await() -> Result<()> {
let block_time = 1u32;
let provider_config = NodeConfig {
block_production: Trigger::Interval {
block_time: std::time::Duration::from_secs(block_time.into()),
},
..NodeConfig::default()
};
// create wallet with 1 coin so that the cache prevents further
// spending unless the coin is invalidated from the cache
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(1), Some(100)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.provider().unwrap();
let tx = create_revert_tx(&wallet).await?;
// Pause time so that the cache doesn't invalidate items based on TTL
tokio::time::pause();
// tx inputs should be cached and then invalidated due to the tx failing
let tx_status = provider.send_transaction_and_await_commit(tx).await?;
assert!(matches!(tx_status, TxStatus::Revert { .. }));
let consensus_parameters = provider.consensus_parameters().await?;
let coins = wallet
.get_spendable_resources(*consensus_parameters.base_asset_id(), 1, None)
.await?;
assert_eq!(coins.len(), 1);
Ok(())
}
#[tokio::test]
async fn can_fetch_mint_transactions() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let provider = wallet.try_provider()?;
let transactions = provider
.get_transactions(PaginationRequest {
cursor: None,
results: 100,
direction: PageDirection::Forward,
})
.await?
.results;
// TODO: remove once (fuels-rs#1093)[https://github.com/FuelLabs/fuels-rs/issues/1093] is in
// until then the type is explicitly mentioned to check that we're reexporting it through fuels
let _: ::fuels::types::transaction::MintTransaction = transactions
.into_iter()
.find_map(|tx| match tx.transaction {
TransactionType::Mint(tx) => Some(tx),
_ => None,
})
.expect("Should have had at least one mint transaction");
Ok(())
}
#[tokio::test]
async fn test_build_with_provider() -> Result<()> {
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?;
let receiver = WalletUnlocked::new_random(Some(provider.clone()));
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
receiver.address(),
*consensus_parameters.base_asset_id(),
100,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let receiver_balance = receiver
.get_asset_balance(consensus_parameters.base_asset_id())
.await?;
assert_eq!(receiver_balance, 100);
Ok(())
}
#[tokio::test]
async fn can_produce_blocks_with_trig_never() -> Result<()> {
let config = NodeConfig {
block_production: Trigger::Never,
..NodeConfig::default()
};
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), Some(config), None)
.await?;
let wallet = &wallets[0];
let provider = wallet.try_provider()?;
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 100, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
&Bech32Address::default(),
*consensus_parameters.base_asset_id(),
100,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = tx.id(consensus_parameters.chain_id());
provider.send_transaction(tx).await?;
provider.produce_blocks(1, None).await?;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
Ok(())
}
#[tokio::test]
async fn can_upload_executor_and_trigger_upgrade() -> Result<()> {
let mut wallet = WalletUnlocked::new_random(None);
// Need more coins to avoid "not enough coins to fit the target"
let num_coins = 100;
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
num_coins,
DEFAULT_COIN_AMOUNT,
);
let mut chain_config = ChainConfig::local_testnet();
chain_config
.consensus_parameters
.set_privileged_address(wallet.address().into());
let provider = setup_test_provider(coins, vec![], None, Some(chain_config)).await?;
wallet.set_provider(provider.clone());
// This is downloaded over in `build.rs`
let executor = std::fs::read(Path::new(env!("OUT_DIR")).join("fuel-core-wasm-executor.wasm"))?;
let subsection_size = 65536;
let subsections = UploadSubsection::split_bytecode(&executor, subsection_size).unwrap();
let root = subsections[0].root;
for subsection in subsections {
let mut builder =
UploadTransactionBuilder::prepare_subsection_upload(subsection, TxPolicies::default());
wallet.add_witnesses(&mut builder)?;
wallet.adjust_for_fee(&mut builder, 0).await?;
let tx = builder.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
}
let mut builder =
UpgradeTransactionBuilder::prepare_state_transition_upgrade(root, TxPolicies::default());
wallet.add_witnesses(&mut builder)?;
wallet.adjust_for_fee(&mut builder, 0).await?;
let tx = builder.build(provider.clone()).await?;
provider.send_transaction(tx).await?;
Ok(())
}
#[tokio::test]
async fn tx_respects_policies() -> Result<()> {
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet",
random_salt = false,
),
);
let tip = 22;
let witness_limit = 1000;
let maturity = 4;
let max_fee = 10_000;
let script_gas_limit = 3000;
let tx_policies = TxPolicies::new(
Some(tip),
Some(witness_limit),
Some(maturity),
Some(max_fee),
Some(script_gas_limit),
);
// advance the block height to ensure the maturity is respected
let provider = wallet.try_provider()?;
provider.produce_blocks(4, None).await?;
// trigger a transaction that contains script code to verify
// that policies precede estimated values
let response = contract_instance
.methods()
.initialize_counter(42)
.with_tx_policies(tx_policies)
.call()
.await?;
let tx_response = provider
.get_transaction_by_id(&response.tx_id.unwrap())
.await?
.expect("tx should exist");
let script = match tx_response.transaction {
TransactionType::Script(tx) => tx,
_ => panic!("expected script transaction"),
};
assert_eq!(script.maturity(), maturity as u32);
assert_eq!(script.tip().unwrap(), tip);
assert_eq!(script.witness_limit().unwrap(), witness_limit);
assert_eq!(script.max_fee().unwrap(), max_fee);
assert_eq!(script.gas_limit(), script_gas_limit);
Ok(())
}
#[tokio::test]
#[ignore] //TODO: https://github.com/FuelLabs/fuels-rs/issues/1581
async fn can_setup_static_gas_price() -> Result<()> {
let expected_gas_price = 474;
let node_config = NodeConfig {
starting_gas_price: expected_gas_price,
..Default::default()
};
let provider = setup_test_provider(vec![], vec![], Some(node_config), None).await?;
let gas_price = provider.estimate_gas_price(0).await?.gas_price;
let da_cost = 1000;
assert_eq!(gas_price, da_cost + expected_gas_price);
Ok(())
}
#[tokio::test]
async fn tx_with_witness_data() -> Result<()> {
use fuel_asm::{op, GTFArgs};
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.try_provider()?;
let receiver = WalletUnlocked::new_random(Some(provider.clone()));
let consensus_parameters = provider.consensus_parameters().await?;
let inputs = wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), 10000, None)
.await?;
let outputs = wallet.get_asset_outputs_for_amount(
receiver.address(),
*consensus_parameters.base_asset_id(),
1,
);
let mut tb = ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet.clone())?;
// we test that the witness data wasn't tempered with during the build (gas estimation) process
// if the witness data is tempered with, the estimation will be off and the transaction
// will error out with `OutOfGas`
let script: Vec<u8> = vec![
// load witness data into register 0x10
op::gtf(0x10, 0x00, GTFArgs::WitnessData.into()),
op::lw(0x10, 0x10, 0x00),
// load expected value into register 0x11
op::movi(0x11, 0x0f),
// load the offset of the revert instruction into register 0x12
op::movi(0x12, 0x08),
// compare the two values and jump to the revert instruction if they are not equal
op::jne(0x10, 0x11, 0x12),
// do some expensive operation so gas estimation is higher if comparison passes
op::gtf(0x13, 0x01, GTFArgs::WitnessData.into()),
op::gtf(0x14, 0x01, GTFArgs::WitnessDataLength.into()),
op::aloc(0x14),
op::eck1(RegId::HP, 0x13, 0x13),
// return the witness data
op::ret(0x10),
op::rvrt(RegId::ZERO),
]
.into_iter()
.collect();
tb.script = script;
let expected_data = 15u64;
let witness = Witness::from(expected_data.to_be_bytes().to_vec());
tb.witnesses_mut().push(witness);
let tx = tb
.with_tx_policies(TxPolicies::default().with_witness_limit(1000))
.build(provider)
.await?;
let status = provider.send_transaction_and_await_commit(tx).await?;
match status {
TxStatus::Success { receipts } => {
let ret: u64 = receipts
.into_iter()
.find_map(|receipt| match receipt {
Receipt::Return { val, .. } => Some(val),
_ => None,
})
.expect("should have return value");
assert_eq!(ret, expected_data);
}
_ => panic!("expected success status"),
}
Ok(())
}
#[tokio::test]
async fn contract_call_with_impersonation() -> Result<()> {
let provider_config = NodeConfig {
utxo_validation: false,
..NodeConfig::default()
};
let mut wallets = launch_custom_provider_and_get_wallets(
WalletsConfig::new(Some(1), Some(10), Some(1000)),
Some(provider_config),
None,
)
.await?;
let wallet = wallets.pop().unwrap();
let provider = wallet.try_provider()?;
let impersonator = ImpersonatedAccount::new(wallet.address().clone(), Some(provider.clone()));
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let contract_id = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy_if_not_exists(&wallet, TxPolicies::default())
.await?;
let contract_instance = MyContract::new(contract_id, impersonator.clone());
// The gas used by the script to call a contract and forward remaining gas limit.
contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
Ok(())
}
#[tokio::test]
async fn is_account_query_test() -> Result<()> {
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let blob = Blob::new(vec![1; 100]);
let blob_id = blob.id();
let is_account = provider.is_user_account(blob_id).await?;
assert!(is_account);
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(provider.clone()).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let is_account = provider.is_user_account(blob_id).await?;
assert!(!is_account);
}
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let contract = Contract::load_from(
"sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?;
let contract_id = contract.contract_id();
let is_account = provider.is_user_account(*contract_id).await?;
assert!(is_account);
contract.deploy(&wallet, TxPolicies::default()).await?;
let is_account = provider.is_user_account(*contract_id).await?;
assert!(!is_account);
}
{
let wallet = launch_provider_and_get_wallet().await?;
let provider = wallet.provider().unwrap().clone();
let mut tb = ScriptTransactionBuilder::default();
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(provider.clone()).await?;
let consensus_parameters = provider.consensus_parameters().await?;
let tx_id = tx.id(consensus_parameters.chain_id());
let is_account = provider.is_user_account(tx_id).await?;
assert!(is_account);
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
let is_account = provider.is_user_account(tx_id).await?;
assert!(!is_account);
}
Ok(())
}
Cookbook
This section covers more advanced use cases that can be satisfied by combining various features of the Rust SDK. As such, it assumes that you have already made yourself familiar with the previous chapters of this book.
Note This section is still a work in progress and more recipes may be added in the future.
Custom chain
This example demonstrates how to start a short-lived Fuel node with custom consensus parameters for the underlying chain.
First, we have to import ConsensusParameters and ChainConfig:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Next, we can define some values for the consensus parameters:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Before we can start a node, we probably also want to define some genesis coins and assign them to an address:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Finally, we call setup_test_provider(), which starts a node with the given configurations and returns a
provider attached to that node:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Deposit and withdraw
Consider the following contract:
contract;
use std::{
asset::{
mint_to,
transfer,
},
call_frames::{
msg_asset_id,
},
constants::ZERO_B256,
context::msg_amount,
};
abi LiquidityPool {
#[payable]
fn deposit(recipient: Identity);
#[payable]
fn withdraw(recipient: Identity);
}
const BASE_TOKEN: AssetId = AssetId::from(0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c);
impl LiquidityPool for Contract {
#[payable]
fn deposit(recipient: Identity) {
assert(BASE_TOKEN == msg_asset_id());
assert(0 < msg_amount());
// Mint two times the amount.
let amount_to_mint = msg_amount() * 2;
// Mint some LP token based upon the amount of the base token.
mint_to(recipient, ZERO_B256, amount_to_mint);
}
#[payable]
fn withdraw(recipient: Identity) {
assert(0 < msg_amount());
// Amount to withdraw.
let amount_to_transfer = msg_amount() / 2;
// Transfer base token to recipient.
transfer(recipient, BASE_TOKEN, amount_to_transfer);
}
}
As its name suggests, it represents a simplified example of a liquidity pool contract. The method deposit() expects you to supply an arbitrary amount of the BASE_TOKEN. As a result, it mints double the amount of the liquidity asset to the calling address. Analogously, if you call withdraw() supplying it with the liquidity asset, it will transfer half that amount of the BASE_TOKEN back to the calling address except for deducting it from the contract balance instead of minting it.
The first step towards interacting with any contract in the Rust SDK is calling the abigen! macro to generate type-safe Rust bindings for the contract methods:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Next, we set up a wallet with custom-defined assets. We give our wallet some of the contracts BASE_TOKEN and the default asset (required for contract deployment):
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Having launched a provider and created the wallet, we can deploy our contract and create an instance of its methods:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
With the preparations out of the way, we can finally deposit to the liquidity pool by calling deposit() via the contract instance. Since we have to transfer assets to the contract, we create the appropriate CallParameters and chain them to the method call. To receive the minted liquidity pool asset, we have to append a variable output to our contract call.
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
As a final demonstration, let's use all our liquidity asset balance to withdraw from the pool and confirm we retrieved the initial amount. For this, we get our liquidity asset balance and supply it to the withdraw() call via CallParameters.
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Transfer all assets
The transfer() method lets you transfer a single asset, but what if you needed to move all of your assets to a different wallet? You could repeatably call transfer(), initiating a transaction each time, or you bundle all the transfers into a single transaction. This chapter guides you through crafting your custom transaction for transferring all assets owned by a wallet.
Lets quickly go over the setup:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
We prepare two wallets with randomized addresses. Next, we want one of our wallets to have some random assets, so we set them up with setup_multiple_assets_coins(). Having created the coins, we can start a provider and assign it to the previously created wallets.
Transactions require us to define input and output coins. Let's assume we do not know the assets owned by wallet_1. We retrieve its balances, i.e. tuples consisting of a string representing the asset ID and the respective amount. This lets us use the helpers get_asset_inputs_for_amount(), get_asset_outputs_for_amount() to create the appropriate inputs and outputs.
We transfer only a part of the base asset balance so that the rest can cover transaction fees:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
All that is left is to build the transaction via ScriptTransactionBuilder, have wallet_1 sign it, and we can send it. We confirm this by checking the number of balances present in the receiving wallet and their amount:
#[cfg(test)]
mod tests {
use std::{str::FromStr, time::Duration};
use fuels::{
accounts::{predicate::Predicate, wallet::WalletUnlocked, ViewOnlyAccount},
prelude::Result,
test_helpers::{setup_single_asset_coins, setup_test_provider},
types::{
bech32::Bech32Address,
transaction::TxPolicies,
transaction_builders::{
BuildableTransaction, ScriptTransactionBuilder, TransactionBuilder,
},
tx_status::TxStatus,
AssetId,
},
};
#[tokio::test]
async fn liquidity() -> Result<()> {
use fuels::{
prelude::*,
test_helpers::{AssetConfig, WalletsConfig},
types::Bits256,
};
// ANCHOR: liquidity_abigen
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool-abi.json"
));
// ANCHOR_END: liquidity_abigen
// ANCHOR: liquidity_wallet
let base_asset_id: AssetId =
"0x9ae5b658754e096e4d681c548daf46354495a437cc61492599e33fc64dcdc30c".parse()?;
let asset_ids = [AssetId::zeroed(), base_asset_id];
let asset_configs = asset_ids
.map(|id| AssetConfig {
id,
num_coins: 1,
coin_amount: 1_000_000,
})
.into();
let wallet_config = WalletsConfig::new_multiple_assets(1, asset_configs);
let wallets = launch_custom_provider_and_get_wallets(wallet_config, None, None).await?;
let wallet = &wallets[0];
// ANCHOR_END: liquidity_wallet
// ANCHOR: liquidity_deploy
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/liquidity_pool/out/release/liquidity_pool.bin",
LoadConfiguration::default(),
)?
.deploy(wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR_END: liquidity_deploy
// ANCHOR: liquidity_deposit
let deposit_amount = 1_000_000;
let call_params = CallParameters::default()
.with_amount(deposit_amount)
.with_asset_id(base_asset_id);
contract_methods
.deposit(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: liquidity_deposit
// ANCHOR: liquidity_withdraw
let lp_asset_id = contract_id.asset_id(&Bits256::zeroed());
let lp_token_balance = wallet.get_asset_balance(&lp_asset_id).await?;
let call_params = CallParameters::default()
.with_amount(lp_token_balance)
.with_asset_id(lp_asset_id);
contract_methods
.withdraw(wallet.address().into())
.call_params(call_params)?
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
let base_balance = wallet.get_asset_balance(&base_asset_id).await?;
assert_eq!(base_balance, deposit_amount);
// ANCHOR_END: liquidity_withdraw
Ok(())
}
#[tokio::test]
async fn custom_chain() -> Result<()> {
// ANCHOR: custom_chain_import
use fuels::{
prelude::*,
tx::{ConsensusParameters, FeeParameters, TxParameters},
};
// ANCHOR_END: custom_chain_import
// ANCHOR: custom_chain_consensus
let tx_params = TxParameters::default()
.with_max_gas_per_tx(1_000)
.with_max_inputs(2);
let fee_params = FeeParameters::default().with_gas_price_factor(10);
let mut consensus_parameters = ConsensusParameters::default();
consensus_parameters.set_tx_params(tx_params);
consensus_parameters.set_fee_params(fee_params);
let chain_config = ChainConfig {
consensus_parameters,
..ChainConfig::default()
};
// ANCHOR_END: custom_chain_consensus
// ANCHOR: custom_chain_coins
let wallet = WalletUnlocked::new_random(None);
let coins = setup_single_asset_coins(
wallet.address(),
Default::default(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
// ANCHOR_END: custom_chain_coins
// ANCHOR: custom_chain_provider
let node_config = NodeConfig::default();
let _provider =
setup_test_provider(coins, vec![], Some(node_config), Some(chain_config)).await?;
// ANCHOR_END: custom_chain_provider
Ok(())
}
#[tokio::test]
async fn transfer_multiple() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
// ANCHOR: transfer_multiple_setup
let mut wallet_1 = WalletUnlocked::new_random(None);
let mut wallet_2 = WalletUnlocked::new_random(None);
const NUM_ASSETS: u64 = 5;
const AMOUNT: u64 = 100_000;
const NUM_COINS: u64 = 1;
let (coins, _) =
setup_multiple_assets_coins(wallet_1.address(), NUM_ASSETS, NUM_COINS, AMOUNT);
let provider = setup_test_provider(coins, vec![], None, None).await?;
wallet_1.set_provider(provider.clone());
wallet_2.set_provider(provider.clone());
// ANCHOR_END: transfer_multiple_setup
// ANCHOR: transfer_multiple_input
let balances = wallet_1.get_balances().await?;
let consensus_parameters = provider.consensus_parameters().await?;
let mut inputs = vec![];
let mut outputs = vec![];
for (id_string, amount) in balances {
let id = AssetId::from_str(&id_string)?;
let amount = amount as u64;
let input = wallet_1
.get_asset_inputs_for_amount(id, amount, None)
.await?;
inputs.extend(input);
// we don't transfer the full base asset so we can cover fees
let output = if id == *consensus_parameters.base_asset_id() {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount / 2)
} else {
wallet_1.get_asset_outputs_for_amount(wallet_2.address(), id, amount)
};
outputs.extend(output);
}
// ANCHOR_END: transfer_multiple_input
// ANCHOR: transfer_multiple_transaction
let mut tb =
ScriptTransactionBuilder::prepare_transfer(inputs, outputs, TxPolicies::default());
tb.add_signer(wallet_1.clone())?;
let tx = tb.build(&provider).await?;
provider.send_transaction_and_await_commit(tx).await?;
let balances = wallet_2.get_balances().await?;
assert_eq!(balances.len(), NUM_ASSETS as usize);
for (id, balance) in balances {
if id == *consensus_parameters.base_asset_id().to_string() {
assert_eq!(balance, (AMOUNT / 2) as u128);
} else {
assert_eq!(balance, AMOUNT as u128);
}
}
// ANCHOR_END: transfer_multiple_transaction
Ok(())
}
#[tokio::test]
#[cfg(any(not(feature = "fuel-core-lib"), feature = "rocksdb"))]
async fn create_or_use_rocksdb() -> Result<()> {
use std::path::PathBuf;
use fuels::prelude::*;
// ANCHOR: create_or_use_rocksdb
let provider_config = NodeConfig {
database_type: DbType::RocksDb(Some(PathBuf::from("/tmp/.spider/db"))),
..NodeConfig::default()
};
// ANCHOR_END: create_or_use_rocksdb
launch_custom_provider_and_get_wallets(Default::default(), Some(provider_config), None)
.await?;
Ok(())
}
#[tokio::test]
async fn custom_transaction() -> Result<()> {
let mut hot_wallet = WalletUnlocked::new_random(None);
let mut cold_wallet = WalletUnlocked::new_random(None);
let code_path = "../../e2e/sway/predicates/swap/out/release/swap.bin";
let mut predicate = Predicate::load_from(code_path)?;
let num_coins = 5;
let amount = 1000;
let bridged_asset_id = AssetId::from([1u8; 32]);
let base_coins =
setup_single_asset_coins(hot_wallet.address(), AssetId::zeroed(), num_coins, amount);
let other_coins =
setup_single_asset_coins(predicate.address(), bridged_asset_id, num_coins, amount);
let provider = setup_test_provider(
base_coins.into_iter().chain(other_coins).collect(),
vec![],
None,
None,
)
.await?;
hot_wallet.set_provider(provider.clone());
cold_wallet.set_provider(provider.clone());
predicate.set_provider(provider.clone());
// ANCHOR: custom_tx_receiver
let ask_amount = 100;
let locked_amount = 500;
let bridged_asset_id = AssetId::from([1u8; 32]);
let receiver = Bech32Address::from_str(
"fuel1p8qt95dysmzrn2rmewntg6n6rg3l8ztueqafg5s6jmd9cgautrdslwdqdw",
)?;
// ANCHOR_END: custom_tx_receiver
// ANCHOR: custom_tx
let tb = ScriptTransactionBuilder::default();
// ANCHOR_END: custom_tx
// ANCHOR: custom_tx_io_base
let consensus_parameters = provider.consensus_parameters().await?;
let base_inputs = hot_wallet
.get_asset_inputs_for_amount(*consensus_parameters.base_asset_id(), ask_amount, None)
.await?;
let base_outputs = hot_wallet.get_asset_outputs_for_amount(
&receiver,
*consensus_parameters.base_asset_id(),
ask_amount,
);
// ANCHOR_END: custom_tx_io_base
// ANCHOR: custom_tx_io_other
let other_asset_inputs = predicate
.get_asset_inputs_for_amount(bridged_asset_id, locked_amount, None)
.await?;
let other_asset_outputs =
predicate.get_asset_outputs_for_amount(cold_wallet.address(), bridged_asset_id, 500);
// ANCHOR_END: custom_tx_io_other
// ANCHOR: custom_tx_io
let inputs = base_inputs
.into_iter()
.chain(other_asset_inputs.into_iter())
.collect();
let outputs = base_outputs
.into_iter()
.chain(other_asset_outputs.into_iter())
.collect();
let mut tb = tb.with_inputs(inputs).with_outputs(outputs);
// ANCHOR_END: custom_tx_io
// ANCHOR: custom_tx_add_signer
tb.add_signer(hot_wallet.clone())?;
// ANCHOR_END: custom_tx_add_signer
// ANCHOR: custom_tx_adjust
hot_wallet.adjust_for_fee(&mut tb, 100).await?;
// ANCHOR_END: custom_tx_adjust
// ANCHOR: custom_tx_policies
let tx_policies = TxPolicies::default().with_tip(1);
let tb = tb.with_tx_policies(tx_policies);
// ANCHOR_END: custom_tx_policies
// ANCHOR: custom_tx_build
let tx = tb.build(&provider).await?;
let tx_id = provider.send_transaction(tx).await?;
// ANCHOR_END: custom_tx_build
tokio::time::sleep(Duration::from_millis(500)).await;
// ANCHOR: custom_tx_verify
let status = provider.tx_status(&tx_id).await?;
assert!(matches!(status, TxStatus::Success { .. }));
let balance = cold_wallet.get_asset_balance(&bridged_asset_id).await?;
assert_eq!(balance, locked_amount);
// ANCHOR_END: custom_tx_verify
Ok(())
}
}
Debugging
note This section is still a work in progress.
Function selector
Whenever you call a contract method the SDK will generate a function selector according to the fuel specs which will be used by the node to identify which method we wish to execute.
If, for whatever reason, you wish to generate the function selector yourself you can do so:
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use fuel_abi_types::abi::unified_program::UnifiedProgramABI;
use fuels::{
core::codec::ABIDecoder,
macros::abigen,
types::{errors::Result, param_types::ParamType, SizedAsciiString},
};
#[test]
fn encode_fn_selector() {
use fuels::core::codec::encode_fn_selector;
// ANCHOR: example_fn_selector
// fn some_fn_name(arg1: Vec<str[3]>, arg2: u8)
let fn_name = "some_fn_name";
let selector = encode_fn_selector(fn_name);
assert_eq!(
selector,
[0, 0, 0, 0, 0, 0, 0, 12, 115, 111, 109, 101, 95, 102, 110, 95, 110, 97, 109, 101]
);
// ANCHOR_END: example_fn_selector
}
#[test]
fn decoded_debug_matches_rust_debug() -> Result<()> {
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/types/contracts/generics/out/release/generics-abi.json"
));
let json_abi_file = "../../e2e/sway/types/contracts/generics/out/release/generics-abi.json";
let abi_file_contents = std::fs::read_to_string(json_abi_file)?;
let parsed_abi = UnifiedProgramABI::from_json_abi(&abi_file_contents)?;
let type_lookup = parsed_abi
.types
.into_iter()
.map(|decl| (decl.type_id, decl))
.collect::<HashMap<_, _>>();
let get_first_fn_argument = |fn_name: &str| {
parsed_abi
.functions
.iter()
.find(|abi_fun| abi_fun.name == fn_name)
.expect("should be there")
.inputs
.first()
.expect("should be there")
};
let decoder = ABIDecoder::default();
{
// simple struct with a single generic parameter
let type_application = get_first_fn_argument("struct_w_generic");
let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?;
let expected_struct = SimpleGeneric {
single_generic_param: 123u64,
};
assert_eq!(
format!("{expected_struct:?}"),
decoder.decode_as_debug_str(¶m_type, [0, 0, 0, 0, 0, 0, 0, 123].as_slice())?
);
}
{
// struct that delegates the generic param internally
let type_application = get_first_fn_argument("struct_delegating_generic");
let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?;
let expected_struct = PassTheGenericOn {
one: SimpleGeneric {
single_generic_param: SizedAsciiString::<3>::try_from("abc")?,
},
};
assert_eq!(
format!("{expected_struct:?}"),
decoder.decode_as_debug_str(¶m_type, [97, 98, 99].as_slice())?
);
}
{
// enum with generic in variant
let type_application = get_first_fn_argument("enum_w_generic");
let param_type = ParamType::try_from_type_application(type_application, &type_lookup)?;
let expected_enum = EnumWGeneric::B(10u64);
assert_eq!(
format!("{expected_enum:?}"),
decoder.decode_as_debug_str(
¶m_type,
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 10].as_slice()
)?
);
}
{
// logged type
let logged_type = parsed_abi
.logged_types
.as_ref()
.expect("has logs")
.first()
.expect("has log");
let param_type =
ParamType::try_from_type_application(&logged_type.application, &type_lookup)?;
let expected_u8 = 1;
assert_eq!(
format!("{expected_u8}"),
decoder.decode_as_debug_str(¶m_type, [1].as_slice())?
);
}
Ok(())
}
}
Decoding script transactions
The SDK offers some tools that can help you make fuel script transactions more human readable. You can determine whether the script transaction is:
- calling a contract method(s),
- is a loader script and you can see the blob id
- is neither of the above
In the case of contract call(s), if you have the ABI file, you can also decode
the arguments to the function by making use of the AbiFormatter:
#[cfg(test)]
mod tests {
use std::{collections::HashSet, time::Duration};
use fuels::{
core::codec::{encode_fn_selector, ABIFormatter, DecoderConfig, EncoderConfig},
crypto::SecretKey,
prelude::{LoadConfiguration, NodeConfig, StorageConfiguration},
programs::debug::ScriptType,
test_helpers::{ChainConfig, StateConfig},
types::{
errors::{transaction::Reason, Result},
Bits256,
},
};
use rand::Rng;
#[tokio::test]
async fn instantiate_client() -> Result<()> {
// ANCHOR: instantiate_client
use fuels::prelude::{FuelService, Provider};
// Run the fuel node.
let server = FuelService::start(
NodeConfig::default(),
ChainConfig::default(),
StateConfig::default(),
)
.await?;
// Create a client that will talk to the node created above.
let client = Provider::from(server.bound_address()).await?;
assert!(client.healthy().await?);
// ANCHOR_END: instantiate_client
Ok(())
}
#[tokio::test]
async fn deploy_contract() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract
// This helper will launch a local node and provide a test wallet linked to it
let wallet = launch_provider_and_get_wallet().await?;
// This will load and deploy your contract binary to the chain so that its ID can
// be used to initialize the instance
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR_END: deploy_contract
Ok(())
}
#[tokio::test]
async fn setup_program_test_example() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deploy_contract_setup_macro_short
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: deploy_contract_setup_macro_short
Ok(())
}
#[tokio::test]
async fn contract_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_cost_estimation
let contract_instance = MyContract::new(contract_id, wallet);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: contract_call_cost_estimation
let expected_gas = 2816;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
async fn deploy_with_parameters() -> std::result::Result<(), Box<dyn std::error::Error>> {
use fuels::{prelude::*, tx::StorageSlot, types::Bytes32};
use rand::prelude::{Rng, SeedableRng, StdRng};
let wallet = launch_provider_and_get_wallet().await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
// ANCHOR: deploy_with_parameters
// Optional: Add `Salt`
let rng = &mut StdRng::seed_from_u64(2322u64);
let salt: [u8; 32] = rng.gen();
// Optional: Configure storage
let key = Bytes32::from([1u8; 32]);
let value = Bytes32::from([2u8; 32]);
let storage_slot = StorageSlot::new(key, value);
let storage_configuration =
StorageConfiguration::default().add_slot_overrides([storage_slot]);
let configuration = LoadConfiguration::default()
.with_storage_configuration(storage_configuration)
.with_salt(salt);
// Optional: Configure deployment parameters
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
configuration,
)?
.deploy(&wallet, tx_policies)
.await?;
println!("Contract deployed @ {contract_id_2}");
// ANCHOR_END: deploy_with_parameters
assert_ne!(contract_id_1, contract_id_2);
// ANCHOR: use_deployed_contract
// This will generate your contract's methods onto `MyContract`.
// This means an instance of `MyContract` will have access to all
// your contract's methods that are running on-chain!
// ANCHOR: abigen_example
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
// ANCHOR_END: abigen_example
// This is an instance of your contract which you can use to make calls to your functions
let contract_instance = MyContract::new(contract_id_2, wallet);
let response = contract_instance
.methods()
.initialize_counter(42) // Build the ABI call
.call() // Perform the network call
.await?;
assert_eq!(42, response.value);
let response = contract_instance
.methods()
.increment_counter(10)
.call()
.await?;
assert_eq!(52, response.value);
// ANCHOR_END: use_deployed_contract
// ANCHOR: submit_response_contract
let response = contract_instance
.methods()
.initialize_counter(42)
.submit()
.await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let value = response.response().await?.value;
// ANCHOR_END: submit_response_contract
assert_eq!(42, value);
Ok(())
}
#[tokio::test]
async fn deploy_with_multiple_wallets() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallets =
launch_custom_provider_and_get_wallets(WalletsConfig::default(), None, None).await?;
let contract_id_1 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallets[0], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_1}");
let contract_instance_1 = MyContract::new(contract_id_1, wallets[0].clone());
let response = contract_instance_1
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
let contract_id_2 = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default().with_salt([1; 32]),
)?
.deploy(&wallets[1], TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id_2}");
let contract_instance_2 = MyContract::new(contract_id_2, wallets[1].clone());
let response = contract_instance_2
.methods()
.initialize_counter(42) // Build the ABI call
.call()
.await?;
assert_eq!(42, response.value);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn contract_tx_and_call_params() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
// ANCHOR: tx_policies
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
let tx_policies = TxPolicies::default()
.with_tip(1)
.with_script_gas_limit(1_000_000)
.with_maturity(0);
let response = contract_methods
.initialize_counter(42) // Our contract method
.with_tx_policies(tx_policies) // Chain the tx policies
.call() // Perform the contract call
.await?; // This is an async call, `.await` it.
// ANCHOR_END: tx_policies
// ANCHOR: tx_policies_default
let response = contract_methods
.initialize_counter(42)
.with_tx_policies(TxPolicies::default())
.call()
.await?;
// ANCHOR_END: tx_policies_default
// ANCHOR: call_parameters
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let tx_policies = TxPolicies::default();
// Forward 1_000_000 coin amount of base asset_id
// this is a big number for checking that amount can be a u64
let call_params = CallParameters::default().with_amount(1_000_000);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_parameters
// ANCHOR: call_parameters_default
let response = contract_methods
.initialize_counter(42)
.call_params(CallParameters::default())?
.call()
.await?;
// ANCHOR_END: call_parameters_default
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn token_ops_tests() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/token_ops/out/release/token_ops-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/token_ops/out/release/token_ops\
.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
println!("Contract deployed @ {contract_id}");
let contract_methods = MyContract::new(contract_id.clone(), wallet.clone()).methods();
// ANCHOR: simulate
// you would mint 100 coins if the transaction wasn't simulated
let counter = contract_methods
.mint_coins(100)
.simulate(Execution::Realistic)
.await?;
// ANCHOR_END: simulate
{
let contract_id = contract_id.clone();
// ANCHOR: simulate_read_state
// you don't need any funds to read state
let balance = contract_methods
.get_balance(contract_id, AssetId::zeroed())
.simulate(Execution::StateReadOnly)
.await?
.value;
// ANCHOR_END: simulate_read_state
}
let response = contract_methods.mint_coins(1_000_000).call().await?;
// ANCHOR: variable_outputs
let address = wallet.address();
let asset_id = contract_id.asset_id(&Bits256::zeroed());
// withdraw some tokens to wallet
let response = contract_methods
.transfer(1_000_000, asset_id, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.call()
.await?;
// ANCHOR_END: variable_outputs
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn dependency_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let called_contract_id: ContractId = Contract::load_from(
"../../e2e/sway/contracts/lib_contract/out/release/lib_contract.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?
.into();
let bin_path =
"../../e2e/sway/contracts/lib_contract_caller/out/release/lib_contract_caller.bin";
let caller_contract_id = Contract::load_from(bin_path, LoadConfiguration::default())?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods =
MyContract::new(caller_contract_id.clone(), wallet.clone()).methods();
// ANCHOR: dependency_estimation_fail
let address = wallet.address();
let amount = 100;
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.call()
.await;
assert!(matches!(
response,
Err(Error::Transaction(Reason::Reverted { .. }))
));
// ANCHOR_END: dependency_estimation_fail
// ANCHOR: dependency_estimation_manual
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::Exactly(1))
.with_contract_ids(&[called_contract_id.into()])
.call()
.await?;
// ANCHOR_END: dependency_estimation_manual
let asset_id = caller_contract_id.asset_id(&Bits256::zeroed());
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, amount);
// ANCHOR: dependency_estimation
let response = contract_methods
.mint_then_increment_from_contract(called_contract_id, amount, address.into())
.with_variable_output_policy(VariableOutputPolicy::EstimateMinimum)
.determine_missing_contracts(Some(2))
.await?
.call()
.await?;
// ANCHOR_END: dependency_estimation
let balance = wallet.get_asset_balance(&asset_id).await?;
assert_eq!(balance, 2 * amount);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn get_contract_outputs() -> Result<()> {
use fuels::prelude::*;
// ANCHOR: deployed_contracts
abigen!(Contract(
name = "MyContract",
// Replace with your contract ABI.json path
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet_original = launch_provider_and_get_wallet().await?;
let wallet = wallet_original.clone();
// Your bech32m encoded contract ID.
let contract_id: Bech32ContractId =
"fuel1vkm285ypjesypw7vhdlhnty3kjxxx4efckdycqh3ttna4xvmxtfs6murwy".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// You can now use the `connected_contract_instance` just as you did above!
// ANCHOR_END: deployed_contracts
let wallet = wallet_original;
// ANCHOR: deployed_contracts_hex
let contract_id: ContractId =
"0x65b6a3d081966040bbccbb7f79ac91b48c635729c59a4c02f15ae7da999b32d3".parse()?;
let connected_contract_instance = MyContract::new(contract_id, wallet);
// ANCHOR_END: deployed_contracts_hex
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn call_params_gas() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: call_params_gas
// Set the transaction `gas_limit` to 1_000_000 and `gas_forwarded` to 4300 to specify that
// the contract call transaction may consume up to 1_000_000 gas, while the actual call may
// only use 4300 gas
let tx_policies = TxPolicies::default().with_script_gas_limit(1_000_000);
let call_params = CallParameters::default().with_gas_forwarded(4300);
let response = contract_methods
.get_msg_amount() // Our contract method.
.with_tx_policies(tx_policies) // Chain the tx policies.
.call_params(call_params)? // Chain the call parameters.
.call() // Perform the contract call.
.await?;
// ANCHOR_END: call_params_gas
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_example() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: multi_call_prepare
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
// ANCHOR_END: multi_call_prepare
// ANCHOR: multi_call_build
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
// ANCHOR_END: multi_call_build
let multi_call_handler_tmp = multi_call_handler.clone();
// ANCHOR: multi_call_values
let (counter, array): (u64, [u64; 2]) = multi_call_handler.call().await?.value;
// ANCHOR_END: multi_call_values
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: multi_contract_call_response
let response = multi_call_handler.call::<(u64, [u64; 2])>().await?;
// ANCHOR_END: multi_contract_call_response
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
let multi_call_handler = multi_call_handler_tmp.clone();
// ANCHOR: submit_response_multicontract
let submitted_tx = multi_call_handler.submit().await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let (counter, array): (u64, [u64; 2]) = submitted_tx.response().await?.value;
// ANCHOR_END: submit_response_multicontract
assert_eq!(counter, 42);
assert_eq!(array, [42; 2]);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn multi_call_cost_estimation() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let wallet = launch_provider_and_get_wallet().await?;
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
let contract_methods = MyContract::new(contract_id, wallet.clone()).methods();
// ANCHOR: multi_call_cost_estimation
let call_handler_1 = contract_methods.initialize_counter(42);
let call_handler_2 = contract_methods.get_array([42; 2]);
let multi_call_handler = CallHandler::new_multi_call(wallet.clone())
.add_call(call_handler_1)
.add_call(call_handler_2);
let tolerance = Some(0.0);
let block_horizon = Some(1);
let transaction_cost = multi_call_handler
.estimate_transaction_cost(tolerance, block_horizon) // Get estimated transaction cost
.await?;
// ANCHOR_END: multi_call_cost_estimation
let expected_gas = 4402;
assert_eq!(transaction_cost.gas_used, expected_gas);
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn connect_wallet() -> Result<()> {
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let config = WalletsConfig::new(Some(2), Some(1), Some(DEFAULT_COIN_AMOUNT));
let mut wallets = launch_custom_provider_and_get_wallets(config, None, None).await?;
let wallet_1 = wallets.pop().unwrap();
let wallet_2 = wallets.pop().unwrap();
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet_1, TxPolicies::default())
.await?;
// ANCHOR: connect_wallet
// Create contract instance with wallet_1
let contract_instance = MyContract::new(contract_id, wallet_1.clone());
// Perform contract call with wallet_2
let response = contract_instance
.with_account(wallet_2) // Connect wallet_2
.methods() // Get contract methods
.get_msg_amount() // Our contract method
.call() // Perform the contract call.
.await?; // This is an async call, `.await` for it.
// ANCHOR_END: connect_wallet
Ok(())
}
#[tokio::test]
async fn custom_assets_example() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let other_wallet = WalletUnlocked::new_random(None);
// ANCHOR: add_custom_assets
let amount = 1000;
let _ = contract_instance
.methods()
.initialize_counter(42)
.add_custom_asset(
AssetId::zeroed(),
amount,
Some(other_wallet.address().clone()),
)
.call()
.await?;
// ANCHOR_END: add_custom_assets
Ok(())
}
#[tokio::test]
async fn low_level_call_example() -> Result<()> {
use fuels::{core::codec::calldata, prelude::*, types::SizedAsciiString};
setup_program_test!(
Wallets("wallet"),
Abigen(
Contract(
name = "MyCallerContract",
project = "e2e/sway/contracts/low_level_caller"
),
Contract(
name = "MyTargetContract",
project = "e2e/sway/contracts/contract_test"
),
),
Deploy(
name = "caller_contract_instance",
contract = "MyCallerContract",
wallet = "wallet"
),
Deploy(
name = "target_contract_instance",
contract = "MyTargetContract",
wallet = "wallet"
),
);
// ANCHOR: low_level_call
let function_selector = encode_fn_selector("set_value_multiple_complex");
let call_data = calldata!(
MyStruct {
a: true,
b: [1, 2, 3],
},
SizedAsciiString::<4>::try_from("fuel")?
)?;
caller_contract_instance
.methods()
.call_low_level_call(
target_contract_instance.id(),
Bytes(function_selector),
Bytes(call_data),
)
.determine_missing_contracts(None)
.await?
.call()
.await?;
// ANCHOR_END: low_level_call
let result_uint = target_contract_instance
.methods()
.get_value()
.call()
.await
.unwrap()
.value;
let result_bool = target_contract_instance
.methods()
.get_bool_value()
.call()
.await
.unwrap()
.value;
let result_str = target_contract_instance
.methods()
.get_str_value()
.call()
.await
.unwrap()
.value;
assert_eq!(result_uint, 2);
assert!(result_bool);
assert_eq!(result_str, "fuel");
Ok(())
}
#[tokio::test]
async fn configure_the_return_value_decoder() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_decoder_config
let _ = contract_instance
.methods()
.initialize_counter(42)
.with_decoder_config(DecoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.call()
.await?;
// ANCHOR_END: contract_decoder_config
Ok(())
}
#[tokio::test]
async fn storage_slots_override() -> Result<()> {
{
// ANCHOR: storage_slots_override
use fuels::{programs::contract::Contract, tx::StorageSlot};
let slot_override = StorageSlot::new([1; 32].into(), [2; 32].into());
let storage_config =
StorageConfiguration::default().add_slot_overrides([slot_override]);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_override
}
{
// ANCHOR: storage_slots_disable_autoload
use fuels::programs::contract::Contract;
let storage_config = StorageConfiguration::default().with_autoload(false);
let load_config =
LoadConfiguration::default().with_storage_configuration(storage_config);
let _: Result<_> = Contract::load_from("...", load_config);
// ANCHOR_END: storage_slots_disable_autoload
}
Ok(())
}
#[tokio::test]
async fn contract_custom_call() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "TestContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "TestContract",
wallet = "wallet"
),
);
let provider = wallet.try_provider()?;
let counter = 42;
// ANCHOR: contract_call_tb
let call_handler = contract_instance.methods().initialize_counter(counter);
let mut tb = call_handler.transaction_builder().await?;
// customize the builder...
wallet.adjust_for_fee(&mut tb, 0).await?;
tb.add_signer(wallet.clone())?;
let tx = tb.build(provider).await?;
let tx_id = provider.send_transaction(tx).await?;
tokio::time::sleep(Duration::from_millis(500)).await;
let tx_status = provider.tx_status(&tx_id).await?;
let response = call_handler.get_response_from(tx_status)?;
assert_eq!(counter, response.value);
// ANCHOR_END: contract_call_tb
Ok(())
}
#[tokio::test]
async fn configure_encoder_config() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Wallets("wallet"),
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
// ANCHOR: contract_encoder_config
let _ = contract_instance
.with_encoder_config(EncoderConfig {
max_depth: 10,
max_tokens: 2_000,
})
.methods()
.initialize_counter(42)
.call()
.await?;
// ANCHOR_END: contract_encoder_config
Ok(())
}
#[tokio::test]
async fn contract_call_impersonation() -> Result<()> {
use std::str::FromStr;
use fuels::prelude::*;
abigen!(Contract(
name = "MyContract",
abi = "e2e/sway/contracts/contract_test/out/release/contract_test-abi.json"
));
let node_config = NodeConfig {
utxo_validation: false,
..Default::default()
};
let mut wallet = WalletUnlocked::new_from_private_key(
SecretKey::from_str(
"0x4433d156e8c53bf5b50af07aa95a29436f29a94e0ccc5d58df8e57bdc8583c32",
)?,
None,
);
let coins = setup_single_asset_coins(
wallet.address(),
AssetId::zeroed(),
DEFAULT_NUM_COINS,
DEFAULT_COIN_AMOUNT,
);
let provider = setup_test_provider(coins, vec![], Some(node_config), None).await?;
wallet.set_provider(provider.clone());
let contract_id = Contract::load_from(
"../../e2e/sway/contracts/contract_test/out/release/contract_test.bin",
LoadConfiguration::default(),
)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR: contract_call_impersonation
// create impersonator for an address
let address =
Address::from_str("0x17f46f562778f4bb5fe368eeae4985197db51d80c83494ea7f84c530172dedd1")
.unwrap();
let address = Bech32Address::from(address);
let impersonator = ImpersonatedAccount::new(address, Some(provider.clone()));
let contract_instance = MyContract::new(contract_id, impersonator.clone());
let response = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?;
assert_eq!(42, response.value);
// ANCHOR_END: contract_call_impersonation
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn deploying_via_loader() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/huge_contract"
)),
Wallets("main_wallet")
);
let contract_binary =
"../../e2e/sway/contracts/huge_contract/out/release/huge_contract.bin";
let provider: Provider = main_wallet.try_provider()?.clone();
let random_salt = || Salt::new(rand::thread_rng().gen());
// ANCHOR: show_contract_is_too_big
let contract = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?;
let max_allowed = provider
.consensus_parameters()
.await?
.contract_params()
.contract_max_size();
assert!(contract.code().len() as u64 > max_allowed);
// ANCHOR_END: show_contract_is_too_big
let wallet = main_wallet.clone();
// ANCHOR: manual_blob_upload_then_deploy
let max_words_per_blob = 10_000;
let blobs = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.blobs()
.to_vec();
let mut all_blob_ids = vec![];
let mut already_uploaded_blobs = HashSet::new();
for blob in blobs {
let blob_id = blob.id();
all_blob_ids.push(blob_id);
// uploading the same blob twice is not allowed
if already_uploaded_blobs.contains(&blob_id) {
continue;
}
let mut tb = BlobTransactionBuilder::default().with_blob(blob);
wallet.adjust_for_fee(&mut tb, 0).await?;
wallet.add_witnesses(&mut tb)?;
let tx = tb.build(&provider).await?;
provider
.send_transaction_and_await_commit(tx)
.await?
.check(None)?;
already_uploaded_blobs.insert(blob_id);
}
let contract_id = Contract::loader_from_blob_ids(all_blob_ids, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blob_upload_then_deploy
// ANCHOR: deploy_via_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: deploy_via_loader
// ANCHOR: auto_convert_to_loader
let max_words_per_blob = 10_000;
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.smart_deploy(&wallet, TxPolicies::default(), max_words_per_blob)
.await?;
// ANCHOR_END: auto_convert_to_loader
// ANCHOR: upload_blobs_then_deploy
let contract_id = Contract::load_from(
contract_binary,
LoadConfiguration::default().with_salt(random_salt()),
)?
.convert_to_loader(max_words_per_blob)?
.upload_blobs(&wallet, TxPolicies::default())
.await?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: upload_blobs_then_deploy
let wallet = main_wallet.clone();
// ANCHOR: use_loader
let contract_instance = MyContract::new(contract_id, wallet);
let response = contract_instance.methods().something().call().await?.value;
assert_eq!(response, 1001);
// ANCHOR_END: use_loader
// ANCHOR: show_max_tx_size
provider
.consensus_parameters()
.await?
.tx_params()
.max_size();
// ANCHOR_END: show_max_tx_size
// ANCHOR: show_max_tx_gas
provider
.consensus_parameters()
.await?
.tx_params()
.max_gas_per_tx();
// ANCHOR_END: show_max_tx_gas
let wallet = main_wallet;
// ANCHOR: manual_blobs_then_deploy
let chunk_size = 100_000;
assert!(
chunk_size % 8 == 0,
"all chunks, except the last, must be word-aligned"
);
let blobs = contract
.code()
.chunks(chunk_size)
.map(|chunk| Blob::new(chunk.to_vec()))
.collect();
let contract_id = Contract::loader_from_blobs(blobs, random_salt(), vec![])?
.deploy(&wallet, TxPolicies::default())
.await?;
// ANCHOR_END: manual_blobs_then_deploy
// ANCHOR: estimate_max_blob_size
let max_blob_size = BlobTransactionBuilder::default()
.estimate_max_blob_size(&provider)
.await?;
// ANCHOR_END: estimate_max_blob_size
Ok(())
}
#[tokio::test]
#[allow(unused_variables)]
async fn decoding_script_transactions() -> Result<()> {
use fuels::prelude::*;
setup_program_test!(
Abigen(Contract(
name = "MyContract",
project = "e2e/sway/contracts/contract_test"
)),
Wallets("wallet"),
Deploy(
name = "contract_instance",
contract = "MyContract",
wallet = "wallet"
)
);
let tx_id = contract_instance
.methods()
.initialize_counter(42)
.call()
.await?
.tx_id
.unwrap();
let provider: &Provider = wallet.try_provider()?;
// ANCHOR: decoding_script_transactions
let TransactionType::Script(tx) = provider
.get_transaction_by_id(&tx_id)
.await?
.unwrap()
.transaction
else {
panic!("Transaction is not a script transaction");
};
let ScriptType::ContractCall(calls) = ScriptType::detect(tx.script(), tx.script_data())?
else {
panic!("Script is not a contract call");
};
let json_abi = std::fs::read_to_string(
"../../e2e/sway/contracts/contract_test/out/release/contract_test-abi.json",
)?;
let abi_formatter = ABIFormatter::from_json_abi(json_abi)?;
let call = &calls[0];
let fn_selector = call.decode_fn_selector()?;
let decoded_args =
abi_formatter.decode_fn_args(&fn_selector, call.encoded_args.as_slice())?;
eprintln!(
"The script called: {fn_selector}({})",
decoded_args.join(", ")
);
// ANCHOR_END: decoding_script_transactions
Ok(())
}
}
prints:
The script called: initialize_counter(42)
The AbiFormatter can also decode configurables, refer to the rust docs for
more information.
Glossary
Contract
A contract, in the SDK, is an abstraction that represents a connection to a specific smart contract deployed on the Fuel Network. This contract instance can be used as a regular Rust object, with methods attached to it that reflect those in its smart contract equivalent.
Provider
A Provider is a struct that provides an abstraction for a connection to a Fuel node. It provides read-only access to the node. You can use this provider as-is or through the wallet.
Wallet and signer
A Wallet is a struct with direct or indirect access to a private key. You can use a Wallet to sign messages and transactions to authorize the network to charge your account to perform operations. The terms wallet and signer in the SDK are often used interchangeably, but, technically, a Signer is simply a Rust trait to enable the signing of transactions and messages; the Wallet implements the Signer trait.
Contributing to the Fuel Rust SDK
Thanks for your interest in contributing to the Fuel Rust SDK!
This document outlines the process for installing dependencies, setting up for development, and conventions for contributing.`
If you run into any difficulties getting started, you can always ask questions on our Discourse.
Finding something to work on
You may contribute to the project in many ways, some of which involve coding knowledge and some which do not. A few examples include:
- Reporting bugs
- Adding new features or bug fixes for which there is already an open issue
- Making feature requests
Check out our Help Wanted or Good First Issues to find a suitable task.
If you are planning something big, for example, changes related to multiple components or changes to current behaviors, make sure to open an issue to discuss with us before starting on the implementation.
Contribution flow
This is a rough outline of what a contributor's workflow looks like:
- Make sure what you want to contribute is already tracked as an issue.
- We may discuss the problem and solution in the issue.
- Create a Git branch from where you want to base your work. This is usually master.
- Write code, add test cases, and commit your work.
- Run tests and make sure all tests pass.
- Add the breaking label to your PR if the PR contains any breaking changes.
- Push your changes to a branch in your fork of the repository and submit a pull request.
- Make sure to mention the issue created in step 1 in the commit message.
- Your PR will be reviewed, and some changes may be requested.
- Your PR must be re-reviewed and approved once you've made changes.
- Use GitHub's 'update branch' button if the PR becomes outdated.
- If there are conflicts, you can merge and resolve them locally. Then push to your PR branch. Any changes to the branch will require a re-review.
- Our CI system (Github Actions) automatically tests all authorized pull requests.
- Use GitHub to merge the PR once approved.
Thanks for your contributions!
Linking issues
Pull requests should be linked to at least one issue in the same repo.
If the pull request resolves the relevant issues, and you want GitHub to close these issues automatically after it merged into the default branch, you can use the syntax (KEYWORD #ISSUE-NUMBER) like this:
close #123
If the pull request links an issue but does not close it, you can use the keyword ref like this:
ref #456
Multiple issues should use full syntax for each issue and be separated by a comma, like:
close #123, ref #456
Integration tests structure in fuels-rs
The integration tests of fuels-rs cover almost all aspects of the SDK and have grown significantly as more functionality was added. To make the tests and associated Sway projects more manageable they were split into several categories. A category consist of a .rs file for the tests and, if needed, a separate directory for the Sway projects.
Currently have the following structure:
.
├─ bindings/
├─ contracts/
├─ logs/
├─ predicates/
├─ storage/
├─ types/
├─ bindings.rs
├─ contracts.rs
├─ from_token.rs
├─ logs.rs
├─ predicates.rs
├─ providers.rs
├─ scripts.rs
├─ storage.rs
├─ types.rs
└─ wallets.rs
Even though test organization is subjective, please consider these guidelines before adding a new category:
- Add a new category when creating a new section in the
Fuels Rust SDKbook - e.g.Types - Add a new category if there are more than 3 test and more than 100 lines of code and they form a group of tests - e.g.
storage.rs
Otherwise, we recommend putting the integration test inside the existing categories above.
fuels-rs Rust Workspaces
This section gives you a little overview of the role and function of every workspace in the fuels-rs repository.
fuels-abi-cli
Simple CLI program to encode Sway function calls and decode their output. The ABI being encoded and decoded is specified here.
Usage
sway-abi-cli 0.1.0
FuelVM ABI coder
USAGE:
sway-abi-cli <SUBCOMMAND>
FLAGS:
-h, --help Prints help information
-V, --version Prints version information
SUBCOMMANDS:
codegen Output Rust types file
decode Decode ABI call result
encode Encode ABI call
help Prints this message or the help of the given subcommand(s)
Examples
You can choose to encode only the given params or you can go a step further and have a full JSON ABI file and encode the whole input to a certain function call defined in the JSON file.
Encoding params only
$ cargo run -- encode params -v bool true
0000000000000001
$ cargo run -- encode params -v bool true -v u32 42 -v u32 100
0000000000000001000000000000002a0000000000000064
Note that for every parameter you want to encode, you must pass a -v flag followed by the type, and then the value: -v <type_1> <value_1> -v <type_2> <value_2> -v <type_n> <value_n>
Encoding function call
example/simple.json:
[
{
"type":"function",
"inputs":[
{
"name":"arg",
"type":"u32"
}
],
"name":"takes_u32_returns_bool",
"outputs":[
{
"name":"",
"type":"bool"
}
]
}
]
$ cargo run -- encode function examples/simple.json takes_u32_returns_bool -p 4
000000006355e6ee0000000000000004
example/array.json
[
{
"type":"function",
"inputs":[
{
"name":"arg",
"type":"u16[3]"
}
],
"name":"takes_array",
"outputs":[
{
"name":"",
"type":"u16[2]"
}
]
}
]
$ cargo run -- encode function examples/array.json takes_array -p '[1,2]'
00000000f0b8786400000000000000010000000000000002
Note that the first word (8 bytes) of the output is reserved for the function selector, which is captured in the last 4 bytes, which is simply the 256hash of the function signature.
Example with nested struct:
[
{
"type":"contract",
"inputs":[
{
"name":"MyNestedStruct",
"type":"struct",
"components":[
{
"name":"x",
"type":"u16"
},
{
"name":"y",
"type":"struct",
"components":[
{
"name":"a",
"type":"bool"
},
{
"name":"b",
"type":"u8[2]"
}
]
}
]
}
],
"name":"takes_nested_struct",
"outputs":[
]
}
]
$ cargo run -- encode function examples/nested_struct.json takes_nested_struct -p '(10, (true, [1,2]))'
00000000e8a04d9c000000000000000a000000000000000100000000000000010000000000000002
Decoding params only
Similar to encoding parameters only:
$ cargo run -- decode params -t bool -t u32 -t u32 0000000000000001000000000000002a0000000000000064
Bool(true)
U32(42)
U32(100)
Decoding function output
$ cargo run -- decode function examples/simple.json takes_u32_returns_bool 0000000000000001
Bool(true)
Getting Started
Welcome to fuels Typescript SDK!
We prepared some guides to walk you through your first steps:
Installation
You must install the Fuel Toolchain before using this library.
Then add the fuels dependency to your project:
::: code-group
bun add fuels@{{fuels}}
pnpm add fuels@{{fuels}}
npm install fuels@{{fuels}} --save
:::
Now you are ready to:
Connecting to the Network
After installing the fuels package, it's easy to connect to the Network:
<<< @./snippets/connecting-to-the-network.ts#main{ts:line-numbers}
RPC URLs
These are our official RPC URLs:
| Network | URL |
|---|---|
| Mainnet | https://testnet.fuel.network/v1/graphql |
| Testnet | https://mainnet.fuel.network/v1/graphql |
| Localhost | Running a local Fuel node |
Resources
Access all our apps directly:
| Mainnet | Testnet | |
|---|---|---|
| Faucet | — | https://faucet-testnet.fuel.network/ |
| Explorer | https://app.fuel.network | https://app-testnet.fuel.network |
| Bridge | https://app.fuel.network/bridge | https://app-testnet.fuel.network/bridge |
| GraphQL | https://mainnet.fuel.network/v1/playground | https://testnet.fuel.network/v1/playground |
Running a local Fuel node
Remember to first install the Fuel Toolchain.
Check the online docs:
| Command | Documentation | |
|---|---|---|
| Fuel Binary | fuel-core | docs — Running a local node |
| TS SDK | fuels node | docs — Using the fuels CLI |
Utilities
Testing
You can run a Fuel node from within your .ts unit tests:
Developing
Configure your project for the fuels CLI using a fuels.config.ts file:
It makes development easier with a hot-reload experience:
More
React Example
import { BN, Provider, Wallet } from "fuels";
import { useEffect, useState } from "react";
function App() {
const [balance, setBalance] = useState(0);
useEffect(() => {
const onPageLoad = async () => {
const provider = new Provider("https://mainnet.fuel.network/v1/graphql");
const wallet = Wallet.fromAddress("0x...", provider);
const { balances } = await wallet.getBalances();
setBalance(new BN(balances[0].amount).toNumber());
};
onPageLoad();
}, []);
return <div>My Balance: {balance}</div>;
}
export default App;
See Also
CDN Usage (browser only)
<script type="module">
import {
Wallet,
Provider,
} from "https://cdnjs.cloudflare.com/ajax/libs/fuels/{{fuels}}/browser.mjs";
const main = async () => {
const provider = new Provider(
"https://mainnet.fuel.network/v1/graphql",
);
const { name } = await provider.getChain();
console.log(name);
};
main();
</script>
More
Done!
Wait no more, let's build your first Fuel dApp!
Further Resources
For a more in-depth, step-by-step guide on working with the wider Fuel ecosystem, check out the Developer Quickstart, which uses a more procedural and detailed approach:
- Installing all tools needed to develop with the Fuel ecosystem
- Writing your first Sway Project
- Deploying your contract
- Building a simple front-end dApp to interact with your deployed contract
The UTXO Model
In UTXO (Unspent Transaction Output) based systems, each coin is unique, similar to how physical currency bills have different denominations.
A UTXO represents a coin with a specific amount, similar to having a $10 or $5 bill. It's crucial to understand this unique feature of UTXOs, as it differs significantly from Ethereum's account-based system.
In Ethereum, balances are tracked as cumulative totals, similar to a bank account, rather than as distinct 'coins' or 'bills'.
Why UTXOs Matter
Each UTXO corresponds to a unique coin and has an associated amount. This model allows for greater transparency and control in cryptocurrency transactions. Understanding UTXOs is key for effectively managing and tracking your digital assets.
How UTXOs Work
When you create a transaction, you will use UTXOs from your account to fund it. Here's a step-by-step explanation:
1. Selecting UTXOs: The SDK selects one or more UTXOs from your account that together are equal to or greater than the transaction amount plus the transaction fee.
2. Spending UTXOs: These selected UTXOs are used to fund the transaction and cover the transaction fee. For example, if you need to send $15 and the transaction fee is $1, and you have $10 and $6 UTXOs, both will be used.
3. New UTXOs: If the total value of the selected UTXOs exceeds the transaction amount plus the transaction fee, the difference is returned to your account as new UTXOs. For instance, if you spend a $20 UTXO for a $15 transaction with a $1 fee, a new UTXO worth $4 will be created as change and added back to your account.
In summary, the original UTXOs used in the transaction are marked as spent and cannot be used again. The new UTXOs are available for future transactions.
Suppose you have the following UTXOs in your account:
- $10 UTXO
- $20 UTXO
You want to send $15 to someone, and the transaction fee is $1. Here's what happens:
- The $20 UTXO is selected to fund the $15 transaction and cover the $1 fee.
- The transaction is completed and the $20 UTXO is spent.
- A new $15 UTXO is generated to the recipient, and a new $4 UTXO (change) is created and added to your account.
Creating a Fuel dApp
npm create fuels is a command line tool that helps you scaffold a new full-stack Fuel dApp. In this guide, we will create a new counter dApp using npm create fuels and add decrement functionality to it. The final result will look like this:

You can also check it live, deployed to the Testnet:
Initializing the project
The first step is to run the command:
::: code-group
npm create fuels@{{fuels}}
pnpm create fuels@{{fuels}}
bun create fuels@{{fuels}}
:::
Once you run the command, you will be asked to choose a name for your project:
◇ What is the name of your project?
│ my-fuel-project
└
The tool will scaffold the project and install the necessary dependencies for you. You will then be greeted with this message:
⚡️ Success! Created a fullstack Fuel dapp at my-fuel-project
To get started:
- cd into the project directory: cd my-fuel-project
- Start a local Fuel dev server: pnpm fuels:dev
- Run the frontend: pnpm dev
-> TS SDK docs: https://docs.fuel.network/docs/fuels-ts/
-> Sway docs: https://docs.fuel.network/docs/sway/
-> If you have any questions, check the Fuel forum: https://forum.fuel.network/
Directory Structure
The project scaffolded by npm create fuels has roughly the following directory structure:
my-fuel-project
├── src
│ ├── components
│ │ └── ...
│ ├── hooks
│ │ └── ...
│ ├── lib.tsx
│ ├── App.tsx
│ └── ...
├── sway-programs
│ ├── contract
│ │ └── ...
│ └── ...
├── public
│ └── ...
├── fuels.config.ts
├── package.json
└── ...
It is a Vite project with a few extra files and folders. Let's take a closer look at some of the important ones:
./fuels.config.ts
This is the configuration file for the fuels CLI, the CLI and tooling that powers this project under the hood. It makes sure that all of your Sway programs are continuously compiled and deployed to your local Fuel node. You can read more about the fuels.config.ts file in the Fuels CLI documentation.
./sway-programs/contract/src/main.sw
This is where our Sway contract lives. Out of the box, it is a simple counter contract that can only be incremented. We will add a decrement functionality to it in the next step.
./src/App.tsx
This file contains the source code for the frontend of our dApp.
./src/components/Contract.tsx
This file contains the source code for the 'Contract' tab in the UI, this is where the contract calling logic is implemented.
Dev Environment Setup
Now that we have our project scaffolded, let's set up our development environment.
Let's first start our Fuel Dev server. This will start a local Fuel node and continuously compile and deploy our Sway programs to it.
::: code-group
npm fuels:dev
pnpm fuels:dev
bun run fuels:dev
:::
Once the server is up and running, we can start our Next.js development server in another terminal.
::: code-group
pnpm dev
pnpm dev
bun run dev
:::
You should now be able to see the dApp running at http://localhost:5173. Go ahead and connect a wallet to the dApp. You can choose the Burner Wallet from the list if you don't want to connect a wallet.

Now, you can try changing the contents of the ./sway-programs/contract/src/main.sw file and see the changes reflected in the 'Contract' tab in the UI without having to restart the server.

Note: You may wish to learn more about how you could create a Fuel dApp that uses predicates, check out our Working with Predicates guide.
Adding Decrement Functionality
To add decrement functionality to our counter, we will have to do two things: 1. Add a decrement_counter function to our Sway contract, and 2. Modify the ./src/components/Contract.tsx file to add a button that calls this function.
1. Modifying the Sway Contract
To add a decrement_counter function to our Sway contract, we will modify the ./sway-programs/contract/src/main.sw file.
There are two steps when adding a new function to a Sway program. The first step is to specify the function's ABI.
Towards the top of the file, you will find the ABI section for the contract. Let's add a new function to it:
<<< @/../../create-fuels-counter-guide/sway-programs/contract/src/main.sw#create-fuels-counter-guide-abi{rust:line-numbers}
The second step is to implement the function.
We will add the implementation of the decrement_counter function right below the increment_counter function.
<<< @/../../create-fuels-counter-guide/sway-programs/contract/src/main.sw#create-fuels-counter-guide-impl{rust:line-numbers}
2. Modifying the Frontend
We will now add a new button to the frontend that will call the decrement_counter function when clicked. To do this, we will modify the ./src/App.tsx file.
First, we will add a function called decrementCounter similar to the incrementCounter function:
<<< @/../../create-fuels-counter-guide/src/components/Contract.tsx#create-fuels-counter-guide-on-decrement-react-function{ts:line-numbers}
Second, we will add a new button to the UI that will call the decrementCounter function when clicked:
<Button onClick={onDecrementPressed} className="mt-6">
Decrement Counter
</Button>
Congratulations! You should now be able to see the counter dApp running at http://localhost:5173 with our newly added decrement functionality.
You can find the complete source code of the dApp we built here.

Whenever you want to add a new feature to your dApp and quickly prototype things, you can follow the same steps we followed in this guide.
3. Extending the contract testing suite (Optional)
Testing our smart contract is a good practice to ensure that our implementation is working as expected. It also give assurances down the line if we decide to change the implementation of our contract.
We write our test in the #[test] macro within our Sway contract, these can be inline within our Sway contract or in a separate file.
For the guide, we'll add a test for our new decrement_counter function in the ./sway-programs/contract/src/main.sw file:
<<< @/../../create-fuels-counter-guide/sway-programs/contract/src/main.sw#create-fuels-counter-guide-sway-contract-test{rust:line-numbers}
After writing our test, we can run either using forc test or via PNPM using pnpm test:forc.
4. Extending the integration test suite (Optional)
Testing the integration with your smart contract isn't essential, but it's good practice to ensure that your application is working as expected. It also gives you the ability to test your application in a controlled environment against a local node.
We've provided some examples for each program type in the ./test directory of your project. But let's also add a test for our new decrement_counter function in the ./test/contract.test.ts file:
<<< @./snippets/decrement-counter.ts#full{ts:line-numbers}
The template also comes with a UI testing setup using Playwright. We can add a test for our new decrement_counter function in the ./test/ui/ui.test.ts file:
<<< @/../../create-fuels-counter-guide/test/ui/ui.test.ts#decrement-counter-ui-test{ts:line-numbers}
Next Steps
-
Now that you have a basic counter dApp running and have the
npm create fuelsworkflow powering you, you can start building more complex dApps using the Fuel Stack. A good place to start for ideas and reference code is the Sway Applications Repo. -
As you may have noticed, there are different types of programs in your dApp, feel free to explore Predicates and Scripts, which are both important differentiators in the Fuel Stack.
-
If you want to deploy your dApp to the testnet, check out our Deploying a dApp to Testnet guide.
-
If you want to further validate the functionality of your dApp and program types, check out the
testdirectory in yourcreate fuelsproject. Couple this with our testing guide to get a better understanding of how to test your dApp. -
If you have any questions or need help, feel free to reach out to us on the Official Fuel Forum.
-
If you want to learn more about the Fuel Stack, check out the Fuel Docs.
Options
The npm create fuels command has several command-line options that you can use to customize your project.
::: code-group
pnpm create fuels@{{fuels}} [project-name] [options]
npm create fuels@{{fuels}} -- [project-name] [options]
bun create fuels@{{fuels}} [project-name] [options]
:::
--template <template-name>
Specifies the template to use for your project. The available templates are: vite and nextjs. The default template is vite.
--verbose
Enables verbose logging. Useful when debugging issues with the tool.
-h, --help
Displays a help message with all available options.
-V, --version
Displays the version number of the npm create fuels command.
Deploying a dApp to Testnet
In this guide, we will deploy a full-stack dApp bootstrapped with npm create fuels to the Fuel testnet.
Make sure you have already bootstrapped a dApp using
npm create fuels. If you haven't, please follow this guide.
There are mainly two steps to get our dApp live on the testnet:
- Deploying the Contract to the Testnet
- Deploying the Frontend to the Cloud
Deploying the Contract
We will be using forc to deploy our contracts to the testnet. forc is a part of the Fuel Toolchain.
If you don't have the Fuel Toolchain installed, follow this guide to install it.
The first step is to cd into the directory containing your contract:
cd sway-programs/contract
And then, run the following command and follow the instructions to deploy the contract to the testnet:
forc deploy --testnet
You can check out this guide for more information on deploying a contract to the testnet.
You should see a message similar to this:
Contract deploy-to-testnet Deployed!
Network: https://testnet.fuel.network
Contract ID: 0x8342d413de2a678245d9ee39f020795800c7e6a4ac5ff7daae275f533dc05e08
Deployed in block 0x4ea52b6652836c499e44b7e42f7c22d1ed1f03cf90a1d94cd0113b9023dfa636
Copy the contract ID and save it for later use.
Deploying the Frontend
Let's now prepare our frontend so that we can deploy it to the cloud.
Go to your .env.local file and add a new variable named VITE_TESTNET_CONTRACT_ID. Set its value to the contract ID you had copied earlier after deploying your contract.
VITE_TESTNET_CONTRACT_ID=0x8342d413de2a678245d9ee39f020795800c7e6a4ac5ff7daae275f533dc05e08
If you are curious, this environment variable is used here in the src/lib.tsx file to set the contract ID:
<<< @/../../create-fuels-counter-guide/src/lib.tsx#deploying-dapp-to-testnet-frontend-contract-id{ts:line-numbers}
You will notice that this piece of code is getting the contract ID depending on the current environment. If the environment is local, it will use the contract ID from the auto-generated contract-ids.json file. Otherwise, for a testnet deployment, it will use the contract ID provided by you.
The CURRENT_ENVIRONMENT variable is defined in the lib.tsx file:
<<< @/../../create-fuels-counter-guide/src/lib.tsx#deploying-dapp-to-testnet-lib-current-environment{ts:line-numbers}
As you can see, it depends on the VITE_DAPP_ENVIRONMENT environment variable. If you go to your .env.local file, you will see that it is set to local by default. If you change this value to testnet, the frontend will now be connected to the testnet instead of your local node.
Go ahead and change the VITE_DAPP_ENVIRONMENT value to testnet in your .env.local file.
If you run your frontend now, you should be able to interact with your contract on the testnet.
To deploy your frontend to the cloud, you can use any service like Vercel. Make sure that you setup your environment variables correctly and that your contract ID is correct. Your environment variables should look something like this:
VITE_DAPP_ENVIRONMENT=testnet
VITE_TESTNET_CONTRACT_ID=0x8342d413de2a678245d9ee39f020795800c7e6a4ac5ff7daae275f533dc05e08
(the rest of the environment variables are optional)
Conclusion
Congratulations! You have successfully deployed your Fuel dApp to the testnet.
To recap, to deploy your dApp to the testnet, you need to:
- Deploy your contract to the testnet using
forc deploy --testnet. - Specify this contract ID in your frontend's environment variables. (
VITE_TESTNET_CONTRACT_ID) - Set the
VITE_DAPP_ENVIRONMENTenvironment variable totestnet.
Working with Predicates
This guide builds on the Creating a Fuel dApp guide. Once you've gotten the dApp there up and running, then you can continue here via clicking the Predicate Example link. We will modify the predicate we created in the previous guide. The final result will look like this:

You can also check it live, deployed to the Testnet:
Adding a Configurable pin
The current predicate functionality we have is a simple one that checks if the user has a pin. We will modify this predicate to accept a configurable pin. This will allow the user to set their own pin.
- Modifying the Predicate Contract
The first step is to modify the predicate contract to accept a configurable pin. We will use the configurable keyword to create an updatable constant to store the pin. We will also modify the main function to check this constant instead of a hardcoded pin.
<<< @/../../docs/sway/configurable-pin/src/main.sw#full{rust:line-numbers}
- Modifying the Frontend
We will now add new button to the frontend that will update the pin in the predicate when clicked. To do this, we will modify the ./src/components/Predicate.tsx file.
We will add a function called changePin, which will use the current pin in state to update the pin in the predicate as well as transfer 1000 to the predicate.
<<< @/../../create-fuels-counter-guide/src/components/Predicate.tsx#change-pin-react-function{ts:line-numbers}
It would also be useful to change the placeholder text.
<input
type="text"
value={predicatePin}
onChange={(e) => setPredicatePin(e.target.value)}
className="w-1/2 bg-gray-800 rounded-md px-2 py-1 mr-3 truncate font-mono"
placeholder="Enter current or new pin"
/>
Finally, we will add a button that calls the changePin function when clicked.
<Button onClick={changePin} className="w-full" disabled={isLoading}>
Change Pin
</Button>
Congratulations! That's all. You should now be able to see the modified predicate dApp running at http://localhost:5173 with our newly added change pin functionality.
You can find the complete source code of the dApp we built here.
Next Steps
-
Now that you have a predicate dApp running and have the
npm create fuelsworkflow powering you, you can start building more complex dApps using the Fuel Stack. A good place to start for ideas and reference code is the Sway Applications Repo. -
If you have any questions or need help, feel free to reach out to us on the Official Fuel Forum.
-
If you want to learn more about the Fuel Stack, check out the Fuel Docs.
Fuels CLI
The quickest way to build full stack Fuel dApps.
fuels init— Creates a newfuels.config.tsfilefuels build— Buildforcworkspace and generate Typescript types for everythingfuels deploy— Deploy workspace contracts and save their IDs to JSON filefuels dev— Start local Fuel Core node andbuild+deployon every file change
Getting started
Imagine you have this file structure:
my-fuel-dapp # NextJS app or similar
├── sway-programs # Forc's workspace
│ ├── src
│ ├── ...
│ └── Forc.toml
├── public
│ └── ...
├── src
│ ├── app
│ ├── ...
├ └── sway-programs-api # Type-safe generated API
└── package.json
Prerequisites
The Fuel Toolchain and its components (namely forc and fuel-core) are pre-requisite for several operations with the Fuels CLI. For example:
- Building out contracts using
fuels buildrequiresforc. - Deploying contracts locally using
fuels deployrequiresfuel-core.
Follow the installation guide if you don't have them installed already.
Installation
Add it to your my-fuel-dapp project:
::: code-group
npm install fuels@{{fuels}} --save
pnpm add fuels@{{fuels}}
bun add fuels@{{fuels}}
:::
Double-checking
npx fuels@{{fuels}} -v
Next Step
Use fuels init to create a fuel.config.ts file.
Config File
Here, you can learn more about all configuration options.
workspace
Relative directory path to Forc workspace.
<<< @/../../demo-fuels/fuels.config.full.ts#workspace{ts:line-numbers}
The property
workspaceis incompatible withcontracts,predicates, andscripts.
contracts
List of relative directory paths to Sway contracts.
<<< @/../../demo-fuels/fuels.config.full.ts#contracts{ts:line-numbers}
The property
contractsis incompatible withworkspace.
predicates
List of relative directory paths to Sway predicates.
<<< @/../../demo-fuels/fuels.config.full.ts#predicates{ts:line-numbers}
The property
predicatesis incompatible withworkspace.
scripts
List of relative directory paths to Sway scripts.
<<< @/../../demo-fuels/fuels.config.full.ts#scripts{ts:line-numbers}
The property
scriptsis incompatible withworkspace.
output
Relative directory path to use when generating Typescript definitions.
<<< @/../../demo-fuels/fuels.config.full.ts#output{ts:line-numbers}
providerUrl
The URL to use when deploying contracts.
<<< @/../../demo-fuels/fuels.config.full.ts#providerUrl{ts:line-numbers}
When
autostartFuelCoreproperty is set totrue, theprovidedUrlis overridden by that of the local short-livedfuel-corenode started by thefuels devcommand.
privateKey
Wallet private key, used when deploying contracts.
This property should ideally come from env — process.env.MY_PRIVATE_KEY.
<<< @/../../demo-fuels/fuels.config.full.ts#privateKey{ts:line-numbers}
When
autostartFuelCoreproperty is set totrue, theprivateKeyis overridden with theconsensusKeyof the local short-livedfuel-corenode started by thefuels devcommand.
snapshotDir
- Used by
fuels devonly.
Relative path to directory containing custom configurations for fuel-core, such as:
chainConfig.jsonmetadata.jsonstateConfig.json
This will take effect only when autoStartFuelCore is true.
<<< @/../../demo-fuels/fuels.config.full.ts#snapshotDir{ts:line-numbers}
autoStartFuelCore
- Used by
fuels devonly.
When set to true, it will automatically:
- Starts a short-lived
fuel-corenode as part of thefuels devcommand - Override property
providerUrlwith the URL for the recently startedfuel-corenode
<<< @/../../demo-fuels/fuels.config.full.ts#autoStartFuelCore{ts:line-numbers}
If set to false, you must spin up a fuel-core node by yourself and set the URL for it via providerUrl.
fuelCorePort
- Used by
fuels devonly.- Ignored when
autoStartFuelCoreis set tofalse.
Port to use when starting a local fuel-core node.
<<< @/../../demo-fuels/fuels.config.full.ts#fuelCorePort{ts:line-numbers}
forcBuildFlags
- Used by
fuels buildandfuels deploy.
Sway programs are compiled in debug mode by default.
Here you can customize all build flags, e.g. to build programs in release mode.
<<< @/../../demo-fuels/fuels.config.full.ts#forcBuildFlags{ts:line-numbers}
Check also:
deployConfig
You can supply a ready-to-go deploy configuration object:
<<< @/../../demo-fuels/fuels.config.full.ts#deployConfig-obj{ts:line-numbers}
Or use a function for crafting dynamic deployment flows:
- If you need to fetch and use configs or data from a remote data source
- If you need to use IDs from already deployed contracts — in this case, we can use the
options.contractsproperty to get the necessary contract ID. For example:
<<< @/../../demo-fuels/fuels.config.full.ts#deployConfig-fn{ts:line-numbers}
onBuild
A callback function that is called after a build event has been successful.
Parameters:
config— The loaded config (fuels.config.ts)
<<< @/../../demo-fuels/fuels.config.full.ts#onBuild{ts:line-numbers}
onDeploy
A callback function that is called after a deployment event has been successful.
Parameters:
config— The loaded config (fuels.config.ts)data— The data (an array of deployed contracts)
<<< @/../../demo-fuels/fuels.config.full.ts#onDeploy{ts:line-numbers}
onDev
A callback function that is called after the fuels dev command has successfully restarted.
Parameters:
config— The loaded config (fuels.config.ts)
<<< @/../../demo-fuels/fuels.config.full.ts#onDev{ts:line-numbers}
onNode
A callback function that is called after the fuels node command has successfully refreshed.
Parameters:
config— The loaded config (fuels.config.ts)
<<< @/../../demo-fuels/fuels.config.full.ts#onNode{ts:line-numbers}
onFailure
Pass a callback function to be called in case of errors.
Parameters:
config— The loaded config (fuels.config.ts)error— Original error object
<<< @/../../demo-fuels/fuels.config.full.ts#onFailure{ts:line-numbers}
forcPath
Path to the forc binary.
When not supplied, will default to using the system binaries (forc).
<<< @/../../demo-fuels/fuels.config.full.ts#forcPath{ts:line-numbers}
fuelCorePath
Path to the fuel-core binary.
When not supplied, will default to using the system binaries (fuel-core).
<<< @/../../demo-fuels/fuels.config.full.ts#fuelCorePath{ts:line-numbers}
Loading environment variables
If you want to load environment variables from a .env file, you can use the dotenv package.
First, install it:
::: code-group
pnpm install dotenv
npm install dotenv
bun install dotenv
:::
Then, you can use it in your fuels.config.ts file:
<<< @/../../create-fuels-counter-guide/fuels.config.ts#fuels-config-file-env{ts:line-numbers}
Commands
The fuels CLI consists of a couple of commands.
fuels init
npx fuels@{{fuels}} help init
Options:
--path <path> Path to project root (default: current directory)
-w, --workspace <path> Relative dir path to Forc workspace
-c, --contracts [paths...] Relative paths to Contracts
-s, --scripts [paths...] Relative paths to Scripts
-p, --predicates [paths...] Relative paths to Predicates
-o, --output <path> Relative dir path for Typescript generation output
--forc-path <path> Path to the `forc` binary
--fuel-core-path <path> Path to the `fuel-core` binary
--auto-start-fuel-core Auto-starts a `fuel-core` node during `dev` command
--fuel-core-port <port> Port to use when starting a local `fuel-core` node for dev mode
-h, --help Display help
Creating a sample fuel.config.ts file:
npx fuels@{{fuels}} init --contracts ./my-contracts/* --output ./src/sway-contracts-api
Using Forc workspaces? Try this instead:
npx fuels@{{fuels}} init --workspace ./sway-programs --output ./src/sway-programs-api
This will give you a minimal configuration:
<<< @/../../demo-fuels/fuels.config.minimal.ts#config{ts:line-numbers}
In a nutshell:
.
├── sway-programs # <— forc workspace
├── src
│ └── sway-programs-api # <— output
├── fuels.config.ts
└── package.json
See more
fuels build
npx fuels@{{fuels}} help build
Options:
--path <path> Path to project root (default: "/Users/anderson/Code/fuel/fuels-ts/apps/docs")
-d, --deploy Deploy contracts after build (auto-starts a `fuel-core` node if needed)
-h, --help Display help
Examples:
npx fuels@{{fuels}} build
- Build all Sway programs under your
workspaceusingforc1 - Generate types for them using
fuels-typegen2
npx fuels@{{fuels}} build --deploy
Using the --deploy flag will additionally:
- Auto-start a short-lived
fuel-corenode if needed (docs) - Run
deployon that node
This is useful when working with contracts because a contract's ID is generated only on deployment.
fuels deploy
npx fuels@{{fuels}} deploy
The fuels deploy command does two things:
- Deploy all Sway contracts under
workspace. - Saves their deployed IDs to:
./src/sway-programs-api/contract-ids.json
{
"myContract1": "0x..",
"myContract2": "0x.."
}
Use it when instantiating your contracts:
<<< @/../../demo-fuels/src/index.test.ts#using-generated-files{ts:line-numbers}
For a complete example, see:
Proxy Contracts Deployment
Automatic deployment of proxy contracts can be enabled in Forc.toml.
For more info, please check these docs:
- Proxy Contracts
- Sway Libs / Upgradability Library
- Sway Standards / SRC-14 - Simple Upgradable Proxies
fuels dev
npx fuels@{{fuels}} dev
The fuels dev command does three things:
- Auto-start a short-lived
fuel-corenode (docs) - Runs
buildanddeployonce at the start - Watches your Forc workspace and repeats the previous step on every change
In
devmode, every time you update a contract on your Forcworkspace, we re-generate type definitions and factory classes for it, following your pre-configuredoutputdirectory. If it's part of another build system running in dev mode (i.e.next dev), you can expect it to re-build / auto-reload as well.
fuels node
npx fuels@{{fuels}} node
Starts a short-lived fuel-core node and requires a fuels.config.ts config file.
Generate one with fuels init:
<<< @/../../demo-fuels/fuels.config.minimal.ts#config{ts:line-numbers}
fuels typegen
Manually generates type definitions and factory classes from ABI JSON files.
npx fuels@{{fuels}} help typegen
Options:
-i, --inputs <path|glob...> Input paths/globals to your Abi JSON files
-o, --output <dir> Directory path for generated files
-c, --contract Generate types for Contracts [default]
-s, --script Generate types for Scripts
-p, --predicate Generate types for Predicates
-S, --silent Omit output messages
For more info, check:
fuels versions
Check for version incompatibilities between your Fuel Toolchain component versions, matching them against the ones supported by the Typescript SDK version that you have.
npx fuels@{{fuels}} versions
┌───────────┬───────────┬────────────────┬─────────────┐
│ │ Supported │ Yours / System │ System Path │
├───────────┼───────────┼────────────────┼─────────────┤
│ Forc │ {{forc}} │ {{forc}} │ forc │
├───────────┼───────────┼────────────────┼─────────────┤
│ Fuel-Core │ {{fuelCore}} │ {{fuelCore}} │ fuel-core │
└───────────┴───────────┴────────────────┴─────────────┘
You have all the right versions! ⚡
ABI Typegen
The JSON ABI file
Whether you want to deploy or connect to a pre-existing smart contract, the JSON ABI file is what makes it possible.
It tells the SDK about the ABI methods in your Smart Contracts and Scripts
Given the following Sway smart contract:
#![allow(unused)] fn main() { contract; abi MyContract { fn test_function() -> bool; } impl MyContract for Contract { fn test_function() -> bool { true } } }
The JSON ABI file would look something like this:
$ cat out/debug/my-test-abi.json
[
{
"type": "function",
"inputs": [],
"name": "test_function",
"outputs": [
{
"name": "",
"type": "bool",
"components": null
}
]
}
]
See also:
Generating Types from ABI
Installation
First we install fuels to our project:
pnpm add fuels@{{fuels}}
Help
A first glance at the docs:
$ pnpm fuels typegen -h
Usage: fuels typegen [options]
Generate Typescript from Sway ABI JSON files
Options:
-i, --inputs <path|glob...> Input paths/globals to your ABI JSON files
-o, --output <dir> Directory path for generated files
-c, --contract Generate types for Contracts [default]
-s, --script Generate types for Scripts
-p, --predicate Generate types for Predicates
-S, --silent Omit output messages
-h, --help Display help
Generating Types for Contracts
You can generate types for a Sway contract using the command below:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types
The path after the input flag -i should point to the file ending in -abi.json produced when the contract was built.
The path after the output flag -o will be the output directory for the generated types.
You can omit the --contract option here since it's the default.
Generating Types for Scripts
To generate types for a Sway script, use the --script flag:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types --script
Generating Types for Predicates
To generate types for a Sway predicate, use the --predicate flag:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types --predicate
See also:
Using Generated Types
After generating types via:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types
We can use these files like so:
<<< @/../../demo-typegen/src/demo.test.ts#typegen-demo-contract-factory-connect{ts:line-numbers}
Contract
Let's use the Contract class to deploy a contract:
<<< @/../../demo-typegen/src/demo.test.ts#typegen-demo-contract-factory-deploy{ts:line-numbers}
Autoloading of Storage Slots
Typegen tries to resolve, auto-load, and embed the Storage Slots for your Contract within the MyContract class. Still, you can override it alongside other options from DeployContractOptions, when calling the deploy method:
<<< @/../../demo-typegen/src/demo.test.ts#typegen-demo-contract-storage-slots{ts:line-numbers}
Script
After generating types via:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types --script
We can use these files like so:
<<< @/../../demo-typegen/src/demo.test.ts#typegen-demo-script{ts:line-numbers}
Predicate
After generating types via:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types --predicate
We can use these files like so:
<<< @/../../demo-typegen/src/demo.test.ts#typegen-demo-predicate{ts:line-numbers}
See also:
Provider
The Provider lets you connect to a Fuel node (docs) and interact with it, encapsulating common client operations in the SDK. Those operations include querying the blockchain for network, block, and transaction-related info (and more), as well as sending transactions to the blockchain.
All higher-level abstractions (e.g. Wallet, Contract) that interact with the blockchain go through the Provider, so it's used for various actions like getting a wallet's balance, deploying contracts, querying their state, etc.
<<< @./snippets/provider-instantiation.ts#provider-instantiation{ts:line-numbers}
You can find more examples of Provider usage here.
Provider Options
You can provide various options on Provider instantiation to modify its behavior.
retryOptions
Calls to a fuel node via the Provider will fail if a connection cannot be established.
Specifying retry options allows you to customize the way you want to handle that failure scenario before ultimately throwing an error.
NOTE: retrying is only done when a connection cannot be established. If the connection is established and the node throws an error, no retry will happen.
You can provide the following settings:
maxRetries- Amount of attempts to retry after initial attempt before failing the call.backoff- Strategy used to define the intervals between attempts.exponential(default): Doubles the delay with each attempt.linear- Increases the delay linearly with each attempt.fixed: Uses a constant delay between attempts.
baseDelay(default 150ms) - Base time in milliseconds for the backoff strategy.
<<< @./snippets/provider-options.ts#retryOptions{ts:line-numbers}
requestMiddleware
Allows you to modify the request object to add additional headers, modify the request's body, and much more.
<<< @./snippets/provider-options.ts#requestMiddleware{ts:line-numbers}
timeout
Specify the timeout in milliseconds after which every request will be aborted.
<<< @./snippets/provider-options.ts#timeout{ts:line-numbers}
fetch
Provide a custom fetch function that'll replace the default fetch call.
Note: If defined, requestMiddleware, timeout and retryOptions are applied to this custom fetch function as well.
<<< @./snippets/provider-options.ts#fetch{ts:line-numbers}
resourceCacheTTL
When using the SDK, it may be necessary to submit multiple transactions from the same account in a short period. In such cases, the SDK creates and funds these transactions, then submits them to the node.
However, if a second transaction is created before the first one is processed, there is a chance of using the same resources (UTXOs or Messages) for both transactions. This happens because the resources used in the first transaction are still unspent until the transaction is fully processed.
If the second transaction attempts to use the same resources that the first transaction has already spent, it will result in one of the following error:
Transaction is not inserted. Hash is already known
Transaction is not inserted. UTXO does not exist: {{utxoID}}
Transaction is not inserted. A higher priced tx {{txID}} is already spending this message: {{messageNonce}}
This error indicates that the resources used by the second transaction no longer exist, as the first transaction already spent them.
To prevent this issue, the SDK sets a default cache for resources to 20 seconds. This default caching mechanism ensures that resources used in a submitted transaction are not reused in subsequent transactions within the specified time. You can control the duration of this cache using the resourceCacheTTL flag. If you would like to disable caching, you can pass a value of -1 to the resourceCacheTTL parameter.
<<< @./snippets/provider-options.ts#cache-utxo{ts:line-numbers}
Note:
If you would like to submit multiple transactions without waiting for each transaction to be completed, your account must have multiple UTXOs available. If you only have one UTXO, the first transaction will spend it, and any remaining amount will be converted into a new UTXO with a different ID.
By ensuring your account has multiple UTXOs, you can effectively use the resourceCacheTTL flag to manage transactions without conflicts. For more information on UTXOs, refer to the UTXOs guide.
Pagination
Pagination is highly efficient when dealing with large sets of data. Because of this some methods from the Provider class support GraphQL cursor pagination, allowing you to efficiently navigate through data chunks.
Pagination Arguments
The pagination arguments object is used to specify the range of data you want to retrieve. It includes the following properties:
after: A cursor pointing to a position after which you want to retrieve items.first: The number of items to retrieve after the specified cursor. This is used in conjunction with theafterargument.before: A cursor pointing to a position before which you want to retrieve items.last: The number of items to retrieve before the specified cursor. This is used in conjunction with thebeforeargument.
<<< @./snippets/pagination.ts#pagination-args{ts:line-numbers}
Page Info
The pageInfo object is included in the GraphQL response for requests that support cursor pagination. It provides crucial metadata about the current page of results, allowing you to understand the pagination state and determine if there are more items to fetch before or after the current set.
endCursor: A cursor representing the last item in the current set of results. It should be used as theafterargument in subsequent queries to fetch the next set of items.hasNextPage: A boolean indicating whether there are more items available after the current set.startCursor: A cursor representing the first item in the current set of results. It should be used as thebeforeargument in subsequent queries to fetch the previous set of items.hasPreviousPage: A boolean indicating whether there are more items available before the current set.
<<< @./snippets/pagination.ts#pagination-page-info{ts:line-numbers}
Using Pagination
One of the methods that supports pagination is the getCoins method. This method receives three parameters:
address: The owner's account addressassetId: The asset ID of the coins (optional)paginationArgs: The pagination arguments (optional)
Basic Pagination
Here is how you can use the getCoins method with pagination:
<<< @./snippets/pagination.ts#pagination-next-page{ts:line-numbers}
Navigating to the Previous Page
You can also use the paginationArgs to navigate to the previous page of results:
<<< @./snippets/pagination.ts#pagination-previous-page{ts:line-numbers}
Valid Combinations
-
Forward Pagination:
Use
afterwithfirstto retrieve items following a cursor.
<<< @./snippets/pagination.ts#pagination-forward-pagination{ts}
-
Backward Pagination:
Use
beforewithlastto retrieve items preceding a cursor.
<<< @./snippets/pagination.ts#pagination-backward-pagination{ts}
Default Behavior
If neither assetId nor paginationArgs are provided, the getCoins method will default to the base asset ID and return the first 100 items:
<<< @./snippets/pagination.ts#pagination-default-args{ts:line-numbers}
Querying the Chain
Once you have set up a provider, you're ready to interact with the Fuel blockchain.
Let's look at a few examples below.
getBaseAssetId
The base asset is the underlying asset used to perform any transaction on a chain. This should be fetched from a provider to then be used in transactions.
<<< @./snippets/functionality/get-base-asset-id.ts#getBaseAssetId{ts:line-numbers}
getCoins
Returns UTXOs coins from an account address, optionally filtered by asset ID. This method supports pagination.
<<< @./snippets/functionality/get-coins-from-provider.ts#getCoins-1{ts:line-numbers}
This method is also implemented on the Account class and can be used without providing the address:
<<< @./snippets/functionality/get-coins-from-account.ts#getCoins-2{ts:line-numbers}
getResourcesToSpend
Returns spendable resources (coins or messages) for a transaction request. It accepts an optional third parameter, excludedIds, to exclude specific UTXO IDs or coin message nonces:
<<< @./snippets/functionality/get-resources-to-spend-from-provider.ts#getResourcesToSpend-1{ts:line-numbers}
This method is also available in the Account class and can be used without providing the address:
<<< @./snippets/functionality/get-resources-to-spend-from-account.ts#getResourcesToSpend-2{ts:line-numbers}
getBalances
Returns the sum of all UTXOs coins and unspent message coins amounts for all assets. Unlike getCoins, it only returns the total amounts, not the individual coins:
<<< @./snippets/functionality/get-balances.ts#getBalances-1{ts:line-numbers}
This method is also available in the Account class and can be used without providing the address parameter:
<<< @./snippets/functionality/get-balances.ts#getBalances-2{ts:line-numbers}
getBlocks
The getBlocks method returns blocks from the blockchain matching the given paginationArgs parameter, supporting pagination. The below code snippet shows how to get the last 10 blocks.
<<< @./snippets/functionality/get-blocks.ts#getBlocks{ts:line-numbers}
getMessageByNonce
You can use the getMessageByNonce method to retrieve a message by its nonce.
<<< @./snippets/functionality/get-messages-by-nonce.ts#getMessageByNonce{ts:line-numbers}
getMessages
You can use the getMessages method to retrieve a list of messages from the blockchain.
<<< @./snippets/functionality/get-messages.ts#getMessages{ts:line-numbers}
getMessageProof
A message proof is a cryptographic proof that a message was included in a block. You can use the getMessageProof method to retrieve a message proof for a given transaction ID and message ID.
You can retrieve a message proof by either using it's block ID:
<<< @./snippets/functionality/get-message-proof-block-id.ts#getMessageProof-blockId{ts:line-numbers}
Or by it's block height:
<<< @./snippets/functionality/get-message-proof-block-height.ts#getMessageProof-blockHeight{ts:line-numbers}
getTransactions
You can use the getTransactions method to retrieve a list of transactions from the blockchain. This is limited to 30 transactions per page.
<<< @./snippets/functionality/get-transactions.ts#getTransactions{ts:line-numbers}
Wallets
Wallets can be used for many important things, for instance:
- Checking your balance;
- Transferring coins to a destination address or contract;
- Signing messages and transactions;
- Paying for network fees when sending transactions or deploying smart contracts.
Wallets Instances
The SDK has multiple classes related to a Wallet instance:
-
Wallet: Works simply like a wrapper providing methods to create and instantiating
WalletUnlockedandWalletLockedinstances. -
WalletLocked: Provides the functionalities for a locked wallet.
-
WalletUnlocked: Provides the functionalities for an unlocked wallet.
-
Account: Provides an abstraction with basic functionalities for wallets or accounts to interact with the network. It is essential to notice that both
WalletLockedandWalletUnlockedextend from theAccountclass.
Let's explore these different approaches in the following sub-chapters.
Note: Keep in mind that you should never share your private/secret key. And in the case of wallets that were derived from a mnemonic phrase, never share your mnemonic phrase. If you're planning on storing the wallet on disk, do not store the plain private/secret key and do not store the plain mnemonic phrase. Instead, use
WalletManagerto encrypt its content first before saving it to disk.
Instantiating Wallets
Wallets can be instantiated in multiple ways within the SDK.
Generating new wallets
To generate a new, unlocked wallet, use the generate method. This method creates a new WalletUnlocked instance, which is immediately ready for use.
<<< @./snippets/instantiating/generate.ts#instantiating-wallets-1{ts:line-numbers}
Instantiating Unlocked Wallets
Creating WalletUnlocked instances of your existing wallets is easy and can be done in several ways:
From a private key:
<<< @./snippets/instantiating/from-private-key.ts#instantiating-wallets-2{ts:line-numbers}
From a mnemonic phrase:
<<< @./snippets/instantiating/from-mnemonic-phrase.ts#instantiating-wallets-3{ts:line-numbers}
From a seed:
<<< @./snippets/instantiating/from-seed.ts#instantiating-wallets-4{ts:line-numbers}
From a Hierarchical Deterministic (HD) derived key:
<<< @./snippets/instantiating/from-hd-derived-key.ts#instantiating-wallets-5{ts:line-numbers}
From a JSON wallet:
<<< @./snippets/instantiating/from-json-wallet.ts#instantiating-wallets-6{ts:line-numbers}
It's possible to instantiate a WalletUnlocked from a WalletLocked:
<<< @./snippets/instantiating/unlock-from-private-key.ts#instantiating-wallets-7{ts:line-numbers}
Instantiating Locked Wallets
You can also instantiate WalletLocked instances using just the wallet address:
<<< @./snippets/instantiating/from-b256-address.ts#instantiating-wallets-8{ts:line-numbers}
Connecting to a Provider
While wallets can be used independently of a Provider, operations requiring blockchain interaction will need one.
Connecting an existing wallet to a Provider:
<<< @./snippets/instantiating/connect-existing-wallet.ts#instantiating-wallets-9{ts:line-numbers}
Instantiating a wallet with a Provider:
<<< @./snippets/instantiating/connect-new-wallet.ts#instantiating-wallets-10{ts:line-numbers}
Creating a wallet from a private key
A new wallet with a randomly generated private key can be created by supplying Wallet.generate.
<<< @./snippets/access.ts#wallets{ts:line-numbers}
Alternatively, you can create a wallet from a Private Key:
<<< @./snippets/instantiating/from-wallet.ts#wallet-from-private-key{ts:line-numbers}
You can obtain an address to a private key using the Signer package
<<< @./snippets/instantiating/signer.ts#signer-address{ts:line-numbers}
Creating a wallet from mnemonic phrases
A mnemonic phrase is a cryptographically-generated sequence of words that's used to derive a private key. For instance: "oblige salon price punch saddle immune slogan rare snap desert retire surprise"; would generate the address 0xdf9d0e6c6c5f5da6e82e5e1a77974af6642bdb450a10c43f0c6910a212600185.
In addition to that, we also support Hierarchical Deterministic Wallets and derivation paths, allowing multiple wallets to be derived from a single root mnemonic. You may recognize a derivation path like:
"m/44'/60'/0'/0/0"
In simple terms, this structure enables the creation of multiple wallet addresses from the same mnemonic phrase.
The SDK gives you two wallets from mnemonic instantiation methods: one that takes a derivation path and one that uses the default derivation path, in case you don't want or don't need to configure that.
Here's how you can create wallets with both mnemonic phrases and derivation paths:
1 - Using the default derivation path m/44'/60'/0'/0/0
<<< @./snippets/mnemonic/from-mnemonic-phrases-1.ts#snippet-full{ts:line-numbers}
2 - Using a Custom Derivation Path
<<< @./snippets/mnemonic/from-mnemonic-phrases-2.ts#snippet-full{ts:line-numbers}
Encrypting and Decrypting
JSON wallets are a standardized way of storing wallets securely. They follow a specific schema and are encrypted using a password. This makes it easier to manage multiple wallets and securely store them on disk. This guide will take you through the process of encrypting and decrypting JSON wallets using the Typescript SDK.
Encrypting a Wallet
We will be calling encrypt from the WalletUnlocked instance which will take a password as the argument. It will encrypt the private key using a cipher and returns the JSON keystore wallet. You can then securely store this JSON wallet.
Here is an example of how you can accomplish this:
<<< @./snippets/encrypting-and-decrypting-wallets.ts#encrypting-and-decrypting-json-wallets-1{ts:line-numbers}
Please note that encrypt must be called within an instance of WalletUnlocked. This instance can only be achieved through passing a private key or mnemonic phrase to a locked wallet.
Decrypting a Wallet
To decrypt the JSON wallet and retrieve your private key, you can call fromEncryptedJson on a Wallet instance. It takes the encrypted JSON wallet and the password as its arguments, and returns the decrypted wallet.
Here is an example:
<<< @./snippets/encrypting-and-decrypting-json-wallets-two.ts#encrypting-and-decrypting-json-wallets-2{ts:line-numbers}
In this example, decryptedWallet is an instance of WalletUnlocked class, now available for use.
Important
Remember to securely store your encrypted JSON wallet and password. If you lose them, there will be no way to recover your wallet. For security reasons, avoid sharing your private key, encrypted JSON wallet or password with anyone.
Checking balances
To check the balance of a specific asset, you can use getBalance method. This function aggregates the amounts of all unspent coins of the given asset in your wallet.
<<< @./snippets/checking-balances.ts#checking-balances-1{ts:line-numbers}
To retrieve the balances of all assets in your wallet, use the getBalances method, it returns an array of CoinQuantity. This is useful for getting a comprehensive view of your holdings.
<<< @./snippets/checking-balances-two.ts#checking-balances-2{ts:line-numbers}
Wallet Transferring
This guide provides instructions for transferring assets between wallets and contracts using the SDK. It includes methods to validate balances and initiate and configure transfer requests.
Transferring Assets Between Accounts
The transfer method initiates a transaction request that transfers an asset from one wallet to another. This method requires three parameters:
- The receiver's wallet address
- The amount of the asset to be transferred
- The ID of the asset to be transferred (optional - defaults to the base asset ID)
Upon execution, this function returns a promise that resolves to a transaction response. To wait for the transaction to be processed, call response.waitForResult().
Example
Here is an example of how to use the transfer function:
<<< @./snippets/wallet-transferring/between-accounts.ts#transferring-assets-1{ts:line-numbers}
In the previous example, we used the transfer method which creates a ScriptTransactionRequest, populates its data with the provided transfer information and submits the transaction.
However, there may be times when you need the Transaction ID before actually submitting it to the node. To achieve this, you can simply call the createTransfer method instead.
This method also creates a ScriptTransactionRequest and populates it with the provided data but returns the request object prior to submission.
<<< @./snippets/wallet-transferring/create-transfer.ts#transferring-assets-2{ts:line-numbers}
Note: Any changes made to a transaction request will alter the transaction ID. Therefore, you should only get the transaction ID after all modifications have been made.
<<< @./snippets/wallet-transferring/create-transfer-2.ts#transferring-assets-3{ts:line-numbers}
Transferring Assets To Multiple Wallets
To transfer assets to multiple wallets, use the Account.batchTransfer method:
<<< @./snippets/transfers/batch-transfer.ts#wallet-transferring-6{ts:line-numbers}
Transferring Assets To Contracts
When transferring assets to a deployed contract, we use the transferToContract method, which shares a similar parameter structure with the transfer method.
However, instead of supplying the target wallet's address, as done in destination.address for the transfer method, we need to provide an instance of Address created from the deployed contract id.
If you have the Contract instance of the deployed contract, you can simply use its id property. However, if the contract was deployed with forc deploy or not by you, you will likely only have its ID in a hex string format. In such cases, you can create an Address instance from the contract ID using new Address('0x123...').
Here's an example demonstrating how to use transferToContract:
<<< @./snippets/wallet-transferring/transferring-to-contracts.ts#transferring-assets-4{ts:line-numbers}
Note: Use transferToContract exclusively for transfers to a contract. For transfers to an account address, use transfer instead.
Transferring Assets To Multiple Contracts
Similar to the Account.batchTransfer method, you can transfer multiple assets to multiple contracts using the Account.batchTransferToContracts method. Here's how it works:
<<< @./snippets/wallet-transferring/transferring-to-multiple-contracts.ts#transferring-assets-5{ts:line-numbers}
Always remember to call the waitForResult() function on the transaction response. That ensures the transaction has been mined successfully before proceeding.
Note: Use batchTransferToContracts solely for transferring assets to contracts. Do not use account addresses with this method. For multiple account transfers, use batchTransfer instead.
Checking Balances
Before you transfer assets, please make sure your wallet has enough funds. Attempting a transfer without enough funds will result in the error: The transaction does not have enough funds to cover its execution.
You can see how to check your balance at the checking-balances page.
Signing
Signing Messages
Signing messages with a wallet is a fundamental security practice in a blockchain environment. It can be used to verify ownership and ensure the integrity of data.
Here's how to use the wallet.signMessage method to sign messages (as string):
<<< @./snippets/signing/sign-message.ts#signing-1{ts:line-numbers}
The signMessage method internally:
- Hashes the message (via
hashMessage) - Signs the hashed message using the wallet's private key
- Returns the signature as a hex string
The hashMessage helper will:
- Performs a SHA-256 hash on the UTF-8 encoded message.
The recoverAddress method from the Signer class will take the hashed message and the signature to recover the signer's address. This confirms that the signature was created by the holder of the private key associated with that address, ensuring the authenticity and integrity of the signed message.
Signing Personal Message
We can also sign arbitrary data, not just strings. This is possible by passing an object containing the personalSign property to the hashMessage and signMessage methods:
<<< @./snippets/signing/sign-personal-message.ts#signing-personal-message{ts:line-numbers}
The primary difference between this personal message signing and message signing is the underlying hashing format.
To format the message, we use a similar approach to a EIP-191:
\x19Fuel Signed Message:\n<message length><message>
Note: We still hash using
SHA-256, unlike Ethereum's EIP-191 which usesKeccak-256.
Signing Transactions
Signing a transaction involves using your wallet to sign the transaction ID (also known as transaction hash) to authorize the use of your resources. Here's how it works:
-
Generate a Signature: Using the wallet to create a signature based on the transaction ID. -
Using the Signature on the transaction: Place the signature in the transaction'switnessesarray. Each Coin / Message input should have a matchingwitnessIndex. This index indicates your signature's location within thewitnessesarray. -
Security Mechanism: The transaction ID is derived from the transaction bytes (excluding thewitnesses). If the transaction changes, the ID changes, making any previous signatures invalid. This ensures no unauthorized changes can be made after signing.
The following code snippet exemplifies how a Transaction can be signed:
<<< @./snippets/signing/sign-transaction.ts#signing-2{ts:line-numbers}
Similar to the sign message example, the previous code used Signer.recoverAddress to get the wallet's address from the transaction ID and the signed data.
When using your wallet to submit a transaction with wallet.sendTransaction(), the SDK already handles these steps related to signing the transaction and adding the signature to the witnesses array. Because of that, you can skip this in most cases:
<<< @./snippets/signing/fund-transaction.ts#signing-3{ts:line-numbers}
Connectors
Fuel Wallet Connectors offer a standardized interface to integrate multiple wallets with your DApps, simplifying wallet integration and ensuring smooth user interactions.
Fuel Connectors
Fuel Connectors are a set of standardized interfaces that provide a way to interact with various wallets and services. They offer a consistent way to interact with different wallets and services, allowing developers to focus on building their applications rather than worrying about wallet integration.
To build your own wallet integration, you can create a custom connector that extends the abstract FuelConnector class. This interface provides a set of methods and events that allow you to interact with the wallet and handle various operations such as connecting, disconnecting, signing messages, and sending transactions.
<<< @./snippets/connectors.ts#fuel-connector-extends{ts:line-numbers}
Properties
The FuelConnector abstract class provides several properties that should be implemented to provide information about the connector.
name
The name property is simply a string on the connector that serves as an identifier and will be displayed to the end-user when selecting a connector.
<<< @./snippets/connectors.ts#fuel-connector-name{ts:line-numbers}
external
The external property is simply a boolean that indicates when a connector is external or not.
Connectors are considered external, or non-native, when they do not support the Fuel Network (e.g. Solana, WalletConnect).
metadata
The metadata property on the connector provides additional information about the connector. This information will be displayed to the end-user when selecting a connector. The following is the structure of the metadata object:
<<< @/../../../packages/account/src/connectors/types/connector-metadata.ts#fuel-connector-metadata{ts:line-numbers}
install
The metadata.install property (required) is used to provide information about how to install the connector.
The install object requires three properties:
-
action(required) - astringthat will contain an action string that will be displayed to the user (e.g. "Install"). -
link(required) - astringthat will contain a URL that will be opened when the user clicks the action. -
description(required) - astringthat will contain a description of the installation process.
<<< @./snippets/connectors.ts#fuel-connector-metadata-install{ts:line-numbers}
image
The metadata.image property (optional) provides an image that will be displayed to the end-user when selecting a connector. The image will be a URL to the image to be displayed (this can be an inline data URI, encoded in base64).
<<< @./snippets/connectors.ts#fuel-connector-metadata-image{ts:line-numbers}
You can even define a light and dark theme for the image by providing an object with the light and dark keys (these will take a similar URI as above).
<<< @./snippets/connectors.ts#fuel-connector-metadata-image-theme{ts:line-numbers}
Events
The FuelConnector class provides a number of events that enable developers to listen for changes in the connector state. As part of implementing a custom connector, you can emit these events to notify the consumer dApp of changes.
accounts
The accounts event is emitted every time a connector's accounts change. The event data is an array of string addresses available on the network.
<<< @./snippets/connectors.ts#fuel-connector-events-accounts{ts:line-numbers}
connectors
The connectors event is emitted when the connectors are initialized. The event data is an array of FuelConnector objects available on the network.
<<< @./snippets/connectors.ts#fuel-connector-events-connectors{ts:line-numbers}
currentConnector
The currentConnector event is emitted every time the current connector changes. The event data is a FuelConnector object that is currently connected.
<<< @./snippets/connectors.ts#fuel-connector-events-currentConnector{ts:line-numbers}
currentAccount
The currentAccount event is emitted every time the current account changes. The event data is a string containing the current account address.
<<< @./snippets/connectors.ts#fuel-connector-events-currentAccount{ts:line-numbers}
connection
The connection event is emitted every time the connection status changes. The event data is a boolean value that is true if the connection is established and false otherwise.
<<< @./snippets/connectors.ts#fuel-connector-events-connection{ts:line-numbers}
networks
The networks event is emitted every time the network changes. The event data will be a Network object containing the current network information.
<<< @./snippets/connectors.ts#fuel-connector-events-networks{ts:line-numbers}
currentNetwork
The currentNetwork event is emitted every time the current network changes. The event data will be a Network object containing the current network information.
<<< @./snippets/connectors.ts#fuel-connector-events-currentNetwork{ts:line-numbers}
assets
The assets event is emitted every time the assets change. The event data will be an array of Asset objects available on the network.
<<< @./snippets/connectors.ts#fuel-connector-events-assets{ts:line-numbers}
abis
The abis event is emitted every time an ABI is added to a connector. The event data will be an array of FuelABI object.
<<< @./snippets/connectors.ts#fuel-connector-events-assets{ts:line-numbers}
Methods
The FuelConnector abstract class provides a number of methods that can be implemented to perform various functions. Not all the methods are required to be implemented; if you choose not to implement a given method, then just don't include it in your connector.
ping
The ping method is used to check if the connector is available and connected.
It will return a promise that resolves to true if the connector is available and connected; otherwise, it will resolve to false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-ping{ts:line-numbers}
version
The version method is used to get the current supported version of the connector. It returns a promise that resolves to an object containing the app and network versions.
The returned version strings can be in a range of formats:
- Caret Ranges (e.g.
^1.2.3) - Tilde Ranges (e.g.
~1.2.3) - Exact Versions (e.g.
1.2.3)
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-version{ts:line-numbers}
isConnected
The isConnected method informs if the connector is currently connected.
It will return a promise that resolves to true if the connector is established and currently connected; otherwise, it will return false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-isConnected{ts:line-numbers}
connect
The connect method initiates the current connectors authorization flow if a connection has not already been made.
It will return a promise that resolves to true if the connection has been established successfully, or false if the user has rejected it.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-connect{ts:line-numbers}
disconnect
The disconnect method revokes the authorization of the current connector (provided by the connect methods).
It will return a promise that resolves to true if the disconnection is successful; otherwise, it will resolve to false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-connect{ts:line-numbers}
accounts
The accounts method should return a list of all the accounts for the current connection.
It returns a promise that resolves to an array of addresses, pointing to the accounts currently available on the network.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-accounts{ts:line-numbers}
currentAccount
The currentAccount method will return the default account address if it's authorized with the connection.
It will return a promise to resolve the issue to an address, or if the account is not authorized for the connection, it will return null.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-currentAccount{ts:line-numbers}
signMessage
The signMessage method initiates the sign message flow for the current connection.
It requires two arguments:
address(string)message(string)
Providing the message signing flow is successful, it will return the message signature (as a string).
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-signMessage{ts:line-numbers}
sendTransaction
The signTransaction method initiates the send transaction flow for the current connection.
It requires two arguments:
address(string)transaction(TransactionRequestLike)
It will return the transaction signature (as a string) if it is successfully signed.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-sendTransaction{ts:line-numbers}
assets
The assets method returns a list of all the assets available for the current connection.
It will return a promise that will resolve to an array of assets (see Asset) that are available on the network.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-assets{ts:line-numbers}
addAsset
The addAsset method adds asset metadata to the connector.
It requires a single argument:
asset(Asset)
It returns a promise that resolves to true if the asset is successfully added; otherwise, it resolves to false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-addAsset{ts:line-numbers}
addAssets
The addAssets method adds multiple asset metadata to the connector.
It requires a single argument:
assets(an Array ofAsset).
Returns a promise that resolves to true if the assets are successfully added; otherwise, resolves to false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-addAssets{ts:line-numbers}
addNetwork
The addNetwork method starts the add network flow for the current connection.
It requires a single argument:
networkUrl(string)
Returns a promise that resolves to true if the network is successfully added; otherwise, false.
It should throw an error if the network is not available or the network already exists.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-addNetwork{ts:line-numbers}
networks
The networks method returns a list of all the networks available for the current connection.
Returns a promise that resolves to an array of available networks (see Network).
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-networks{ts:line-numbers}
currentNetwork
The currentNetwork method will return the current network that is connected.
It will return a promise that will resolve to the current network (see Network).
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-currentNetwork{ts:line-numbers}
selectNetwork
The selectNetwork method requests the user to select a network for the current connection.
It requires a single argument:
network(Network)
You call this method with either the providerUrl or chainId to select the network.
It will return a promise that resolves to true if the network is successfully selected; otherwise, it will return false.
It should throw an error if the network is not available or the network does not exist.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-selectNetwork{ts:line-numbers}
addABI
The addABI method adds ABI information about a contract to the connector. This operation does not require an authorized connection.
It requires two arguments:
contractId(string)abi(FuelABI).
It will return a promise that will resolve to true if the ABI is successfully added; otherwise false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-addABI{ts:line-numbers}
getABI
The getABI method is used to get the ABI information that is sorted about a contract.
It requires a single argument:
contractId(string)
Returns a promise that resolves to the ABI information (as a FuelABI) or null if the data is unavailable.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-getABI{ts:line-numbers}
hasABI
The hasABI method checks if the ABI information is available for a contract.
It requires a single argument:
contractId(string)
Returns a promise that resolves to true if the ABI information is available; otherwise false.
<<< @/../../../packages/account/src/connectors/fuel-connector.ts#fuel-connector-method-hasABI{ts:line-numbers}
Connectors Manager
The TS SDK exports the Fuel class, which serves as the connectors manager. This class provides the interface for interacting with the TS SDK and the broader Fuel ecosystem.
It can be instantiated as follows:
<<< @./snippets/fuel-instantiation-one.ts#fuel-instantiation-1{ts:line-numbers}
[!NOTE] Note We recommend initializing the Fuel class with the
initmethod to avoid any potential race conditions that may arise from the async nature of instantiating a connector.
Options
Several options can be passed to the Fuel connector manager:
connectors
The connectors option provides a list of connectors with which the Fuel connector manager can interact. The manager interacts with the connectors, which in turn handle communication with the respective wallet. You can find a list of all the connectors in our FuelLabs/fuel-connectors.
Below, we initialize the manager using the defaultConnectors method which provides an array of all the default connectors available in the fuel-connectors package. It's being mocked here for the purposes of this example, but you can provide your own custom connectors. Supplying the devMode flag as true will enable the development wallet for the connectors (to install visit our wallet documentation).
<<< @./snippets/fuel-instantiation-options.ts#fuel-options-connectors{ts:line-numbers}
storage
The storage is used internally to store the current connector state. It can be overridden by passing an instance that extends the StorageAbstract class.
<<< @./snippets/fuel-options-storage-memory.ts#fuel-options-storage-memory{ts:line-numbers}
The default behavior will use LocalStorage if the window is available:
<<< @./snippets/fuel-options-storage-local.ts#fuel-options-storage-local{ts:line-numbers}
targetObject
The targetObject provides a target with which the Fuel manager can interact. Used for registering events and can be overridden as follows:
<<< @./snippets/fuel-options-target-object.ts#fuel-options-target-object{ts:line-numbers}
Methods
The Fuel manager provides several methods to interact with the Manager:
All methods from connectors
The Fuel manager provides all the methods available from the connected connectors. Thus, you can interact with the current connector as if you were interacting with the Fuel manager directly.
If no current connector is available or connected, it will throw an error.
connectors
The connectors method gets the current list of installed and connected connectors.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-connectors{ts:line-numbers}
getConnector
The getConnector method resolves a connector by its name. This is useful for finding a specific connector with which to interact. If the connector is not found, it will return null.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-getConnector{ts:line-numbers}
hasConnector
The hasConnector method will return true under the following conditions:
- There is a current connector that is connected.
- A connector is connected within two seconds of calling the method.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-hasConnector{ts:line-numbers}
selectConnector
The selectConnector method accepts a connector name and will return true when it is available and connected. Otherwise, if not found or unavailable, it will return false.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-selectConnector{ts:line-numbers}
currentConnector
The currentConnector method will return the current connector that is connected or if one is available and connected, otherwise it'll return null or undefined.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-currentConnector{ts:line-numbers}
getWallet
The getWallet method accepts an address (string or instance) as the first parameter and a provider or network as the second parameter. It will return an Account instance for the given address (providing it is valid).
The provider or network will default to the current network if not provided. When a provider cannot be resolved, it will throw an INVALID_PROVIDER error.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-getWallet{ts:line-numbers}
clean
The clean method removes all the data currently stored in the storage instance.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-clean{ts:line-numbers}
unsubscribe
The unsubscribe method removes all currently registered event listeners.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-unsubscribe{ts:line-numbers}
destroy
The destroy method unsubscribes from all the event listeners and clears the storage.
<<< @/../../../packages/account/src/connectors/fuel.ts#connector-manager-method-destroy{ts:line-numbers}
Learning Resources
For a deeper understanding of Fuel Connectors and how to start using them in your projects, consider the following resources:
- Fuel Connectors Wiki - read about what a
Fuel Connectoris and how it works. - Fuel Connectors Guide - find out how to set up and use connectors.
- GitHub Repository - explore different connector implementations.
Wallet Manager
The WalletManager is a robust tool designed for managing vaults of wallets. It offers robust management of vaults, including support for custom storage and powerful encryption of all held vaults.
Key Features
Managing Vaults with WalletManager
This includes adding new wallets to specific vaults, retrieving all wallets from a vault, exporting specific vaults, and exporting private keys. The WalletManager class currently supports two types of vaults: PrivateKeyVault and MnemonicVault.
Custom Storage Solutions
The WalletManager supports defining a custom storage solution, allowing you to specify how and where the encrypted vaults are saved. With support for custom storage, you can make the WalletManager to fit your specific needs and security requirements.
Locking and Unlocking WalletManager
The WalletManager implements an automatic encryption mechanism, securely saving the wallet's held vaults. This not only preserves the state of your vaults but also ensures robust protection of the stored information. When needed, you can easily unlock and decrypt the vaults using the previously defined password.
Getting Started with WalletManager
This guide provides step-by-step instructions on how to use WalletManager.
Instantiating WalletManager
The WalletManager constructor accepts an optional object to define its storage. The storage describes how and where the WalletManager will store its vaults of wallets. If storage is not provided, the WalletManager uses a default one that does not persist data.
For now, let's keep it simple and not worry about the storage. Later we will discuss it in more detail.
To instantiate a WalletManager you can simply:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-1{ts:line-numbers}
Setting WalletManager Password
By default, a WalletManager instance is locked when created. Before using it, you need to unlock it by setting a password. You can do this by calling the unlock method.
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-2{ts:line-numbers}
Once your WalletManager is unlocked, it can manage your wallets.
Managing Vaults with WalletManager
A vault in WalletManager serves as a secure container for wallets. The WalletManager manages wallets by interacting with these vaults, supporting operations such as getAccounts, which returns public information about all wallets stored in the vault, and exportAccount, which exports a private key for a given wallet address.
To add a vault, we utilize the addVault method. Here's how we can create a private key vault and add a private key from a wallet we own:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-3{ts:line-numbers}
The addVault method requires an object with three properties: type, secret, and title. The WalletManager currently supports two types of vaults: privateKeyVault and mnemonicVault. For the secret, we use our wallet's private key, and for the title, we can provide a custom name.
By running this code, WalletManager creates a new vault instance of the type privateKey and adds one account (our wallet) to this newly created vault.
A key feature of the WalletManager is its ability to manage multiple vaults, even of the same type. This implies that if you run the addVault method again, with the same parameters, WalletManager will create another vault of the type privateKey, holding the same wallet. Here's an example:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-4{ts:line-numbers}
After executing this, you will find that your WalletManager is managing two privateKey vaults, both storing the same wallet.
Remember, both title and secret are optional when adding vaults, but providing a title makes it easier to manage your vaults and wallets. If you add a vault without providing a secret, this will result in one new account (wallet) being generated by the vault it self.
Using The WalletManager
With your WalletManager set up, you can now access your vaults and wallets. Here's how to retrieve the details of your vaults:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-5{ts:line-numbers}
This will output something like this:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-6{bash:line-numbers}
As you can see, the WalletManager assigns unique vaultIds for each vault. The first vault you added has a vaultId of 0, and the second one has a vaultId of 1.
Let's retrieve your wallet instance with the getWallet method:
<<< @./snippets/getting-started-with-wallet-manager.ts#getting-started-with-wallet-manager-7{ts:line-numbers}
This guide walked through the steps to instantiate a WalletManager, set up its first vault, and retrieve vault information. The following sections will explore more functionalities of WalletManager, and go deeper into the usage of its vaults and the details of its storage system.
Locking and Unlocking WalletManager
This guide will walk you through the process of managing the lock state of your wallets using the WalletManager.
Initializing and Unlocking the WalletManager
As mentioned earlier, a WalletManager instance begins in a locked state. Before usage, you need to unlock it by providing a password via the unlock method.
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-1{ts:line-numbers}
Locking the WalletManager
When you lock the WalletManager using the lock method, all its vaults and associated accounts (wallets) are cleared. This clearance is possible due to the encryption and saving of all data by the storage system. WalletManager frequently uses the storage system to preserve its state. Consequently, sensitive operations including exporting vaults, private keys, accessing wallets, and saving/loading the WalletManager state are not possible when it is locked.
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-2{ts:line-numbers}
Remember, it's crucial to lock your WalletManager when it's not in use to ensure the safety of your funds.
Reaccessing Your Wallets by Unlocking the WalletManager
The unlock method requires the previously set password to unlock the WalletManager and all its vaults. The password decrypts the stored vaults, allowing WalletManager to load its saved data.
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-3{ts:line-numbers}
Providing an incorrect password will result in an error. However, when unlocked successfully, WalletManager is ready for use again.
Verifying the Lock State
You can confirm the current lock state of the WalletManager by using the isLocked method:
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-4{ts:line-numbers}
Updating the Password
To change the current password, invoke the updatePassphrase method, and provide both the old and new passwords:
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-5{ts:line-numbers}
Reminder: Always Lock Your WalletManager
Always ensure you lock the WalletManager after completing operations. This step is critical for securing your wallets.
<<< @./snippets/locking-and-unlocking-wallet-manager.ts#locking-and-unlocking-wallet-manager-6{ts:line-numbers}
By using WalletManager to manage lock and unlock states, you introduce an additional layer of security. Never forget to lock your WalletManager when it's not in use.
Locking and Unlocking
The kinds of operations we can perform with a Wallet instance depend on
whether or not we have access to the wallet's private key.
In order to differentiate between Wallet instances that know their private key
and those that do not, we use the WalletUnlocked and WalletLocked types
respectively.
Wallet States
The WalletUnlocked type represents a wallet whose private key is known and
stored internally in memory. A wallet must be of type WalletUnlocked in order
to perform operations that involve signing messages or transactions.
The WalletLocked type represents a wallet whose private key is not known or stored
in memory. Instead, WalletLocked only knows its public address. A WalletLocked cannot be
used to sign transactions, however it may still perform a whole suite of useful
operations including listing transactions, assets, querying balances, and so on.
Note that the WalletUnlocked type implements most methods available on the WalletLocked
type. In other words, WalletUnlocked can be thought of as a thin wrapper around WalletLocked that
provides greater access via its private key.
Basic Example
<<< @./snippets/access.ts#wallets{ts:line-numbers}
Optional Provider
You can choose not to pass through a provider argument on Wallet construction:
<<< @./snippets/wallet-optional-provider.ts#wallet-optional-provider{ts:line-numbers}
Transitioning States
A WalletLocked instance can be unlocked by providing the private key:
<<< @./snippets/locked-to-unlocked.ts#wallet-locked-to-unlocked{ts:line-numbers}
A WalletUnlocked instance can be locked using the lock method:
<<< @./snippets/unlocked-to-locked.ts#wallet-unlocked-to-locked{ts:line-numbers}
Most wallet constructors that create or generate a new wallet are provided on
the WalletUnlocked type. Consider locking the wallet with the lock method after the new private
key has been handled in order to reduce the scope in which the wallet's private
key is stored in memory.
Design Guidelines
When designing APIs that accept a wallet as an input, we should think carefully
about the kind of access that we require. API developers should aim to minimise
their usage of WalletUnlocked in order to ensure private keys are stored in
memory no longer than necessary to reduce the surface area for attacks and
vulnerabilities in downstream libraries and applications.
Full Example
For a full example of how to lock and unlock a wallet, see the snippet below:
<<< @./snippets/access.ts#full{ts:line-numbers}
Contracts
In the Fuel Network, contracts play a crucial role in facilitating interactions between users and the decentralized applications built on top of the network. Once you've deployed a contract, you may want to perform various tasks such as:
- Calling contract methods;
- Configuring call and transaction parameters like gas price, byte price, and gas limit;
- Forwarding coins and gas in your contract calls;
- Reading and interpreting returned values and logs.
For instance, consider a Sway contract with two ABI methods called echo_str_8(str[8]) and echo_u8(u8). After deploying the contract, you can call the methods as follows:
<<< @./snippets/introduction.ts#method-calls{ts:line-numbers}
The example above demonstrates a simple contract call using default configurations. The following sections will explore how to further configure various parameters for contract calls, allowing for more advanced interactions with your deployed contracts in the Fuel Network.
Interacting With Contracts
There are 4 ways to interact with contracts: get, dryRun, simulate, call.
get
The get method should be used to read data from the blockchain without using resources. It can be used with an unfunded wallet or even without a wallet at all:
<<< @./snippets/methods/get.ts#interacting-with-contracts-1{ts:line-numbers}
dryRun
The dryRun method should be used to dry-run a contract call. It does not spend resources and can be used with an unfunded wallet or even without a wallet at all:
<<< @./snippets/methods/dry-run.ts#interacting-with-contracts-2{ts:line-numbers}
simulate
The simulate method should be used to dry-run a contract call, ensuring that the wallet used has sufficient funds to cover the transaction fees, without consuming any resources.
A funded wallet it's required:
<<< @./snippets/methods/simulate.ts#interacting-with-contracts-3{ts:line-numbers}
call
The call method submits a real contract call transaction to the node, resolving immediately upon submission and returning a transactionId along with a waitForResult callback to wait for transaction execution. This behavior aligns with the natural behaviour of blockchains, where transactions may take a few seconds before being recorded on the chain.
Real resources are consumed, and any operations executed by the contract function will be processed on the blockchain.
<<< @./snippets/methods/call.ts#interacting-with-contracts-4{ts:line-numbers}
isReadOnly (utility)
If you want to figure out whether a function is read-only, you can use the isReadOnly method:
<<< @./snippets/methods/is-read-only.ts#is-function-readonly-1{ts:line-numbers}
If the function is read-only, you can use the get method to retrieve onchain data without spending gas.
If the function is not read-only you will have to use the call method to submit a transaction onchain which incurs a gas fee.
Call Parameters
When interacting with contracts, you can configure specific parameters for contract calls using the callParams method. The available call parameters are:
forwardgasLimit
Note: Setting transaction parameters is also available when calling contracts. More information on this can be found at Transaction Parameters.
The contract in use in this section has the following implementation:
<<< @/../../docs/sway/return-context/src/main.sw#return-context-contract{rust:line-numbers}
Forward Parameter
The forward parameter allows the sending of a specific amount of coins to a contract when a function is called. This is useful when a contract function requires coins for its execution, such as paying fees or transferring funds. The forward parameter helps you control the resources allocated to the contract call and offers protection against potentially costly operations.
<<< @./snippets/call-parameters/forward.ts#forward{ts:line-numbers}
Gas Limit Parameter
The gasLimit refers to the maximum amount of gas that can be consumed specifically by the contract call itself, separate from the rest of the transaction.
<<< @./snippets/call-parameters/gas-fee.ts#gas-fee{ts:line-numbers}
Call Parameter gasLimit vs Transaction Parameter gasLimit
The call parameter gasLimit sets the maximum gas allowed for the actual contract call, whereas the transaction parameter gasLimit (see Transaction Parameters) sets the maximum gas allowed for the entire transaction and constrains the gasLimit call parameter. If the call parameter gasLimit is set to a value greater than the available transaction gas, then the entire available transaction gas will be allocated for the contract call execution.
If you don't set the gasLimit for the call, the transaction gasLimit will be applied.
Setting Both Parameters
You can set both call parameters and transaction parameters within the same contract function call.
<<< @./snippets/call-parameters/setting-both-parameters.ts#setting-both-parameters{ts:line-numbers}
Contract Balance
When working with contracts, it's crucial to be aware of the available contract balance of an asset while paying for costly operations. This guide will explain the getBalance method in the Contract class, which allows you to check a contract's available balance.
The getBalance Method
The Contract.getBalance method retrieves the available balance of a specific asset on your contract. This method is particularly useful for determining the remaining balance after sending assets to a contract and executing contract calls.
It is important to note that this method returns the total available contract balance, regardless of how often assets have been sent to it or spent.
Checking Contract Balance
Consider a simple contract that transfers a specified amount of a given asset to an address:
<<< @/../../docs/sway/transfer-to-address/src/main.sw#full{rust:line-numbers}
The transfer function has three parameters:
-
amount_to_transfer: The amount that is being transferred. -
asset: The address of the deployed contract token. -
recipient: The address of the receiver's wallet.
The transfer function calls the built-in Sway function transfer_to_address, which does precisely what the name suggests.
Let's execute this contract and use the getBalance method to validate the remaining asset amount the contract has left to spend.
<<< @./snippets/contract-balance.ts#example{ts:line-numbers}
In this example, we first forward an asset amount greater than the amount required for the transfer, and then we execute the contract call.
Finally, we use the getBalance method to confirm that the contract balance is precisely the total forwarded amount minus the transferred amount.
Estimating Contract Call Cost
The FunctionInvocationScope.getTransactionCost method allows you to estimate the cost of a specific contract call. The return type, TransactionCost, is an object containing relevant information for the estimation:
<<< @/../../../packages/account/src/providers/provider.ts#cost-estimation-1{ts:line-numbers}
The following example demonstrates how to get the estimated transaction cost for:
1. Single contract call transaction:
<<< @./snippets/cost-estimation.ts#cost-estimation-1{ts:line-numbers}
2. Multiple contract calls transaction:
<<< @./snippets/cost-estimation.ts#cost-estimation-2{ts:line-numbers}
You can use the transaction cost estimation to set the gas limit for an actual call or display the estimated cost to the user.
Transaction Dependency Estimation
In variable outputs, we mention that a contract call might require you to manually specify external contracts or variable outputs.
However, by default the SDK always automatically estimates these dependencies and double-checks if everything is in order whenever you invoke a contract function or attempt to send a transaction.
The SDK uses the Provider.estimateTxDependencies method to set any missing dependencies identified during the estimation process. This requires simulating the transaction a few times in the background.
While relying on the SDK's automatic estimation is a decent default behavior, we recommend manually specifying the dependencies if they are known in advance to avoid the performance impact of the estimation process.
Variable Outputs
Sway includes robust functions for transferring assets to wallets and contracts.
When using these transfer functions within your Sway projects, it is important to be aware that each call will require an Output Variable within the Outputs of the transaction.
For instance, if a contract function calls a Sway transfer function 3 times, it will require 3 Output Variables present within the list of outputs in your transaction.
Example: Sway functions that requires Output Variable
<<< @/../../docs/sway/token/src/main.sw#variable-outputs-1{ts:line-numbers}
Adding Variable Outputs to the contract call
When your contract invokes any of these functions, or if it calls a function that leads to another contract invoking these functions, you need to add the appropriate number of Output Variables.
This can be done as shown in the following example:
<<< @./snippets/utilities/variable-outputs.ts#variable-outputs-2{ts:line-numbers}
In the TypeScript SDK, the Output Variables are automatically added to the transaction's list of outputs.
This process is done by a brute-force strategy, performing sequential dry runs until no errors are returned. This method identifies the number of Output Variables required to process the transaction.
However, this can significantly delay the transaction processing. Therefore, it is highly recommended to manually add the correct number of Output Variables before submitting the transaction.
Working with Contract Logs
When you log a value within a contract method, it generates a log entry that is added to the log receipt, and the variable type is recorded in the contract's ABI. The SDK enables you to parse these values into TypeScript types.
Consider the following example contract:
<<< @/../../docs/sway/log-values/src/main.sw#log-1{rust:line-numbers}
To access the logged values in TypeScript, use the logs property in the response of a contract call. The logs data will be stored in an Array<any>:
<<< @./snippets/logs.ts#full{ts:line-numbers}
This approach allows you to work seamlessly with logged values in your contract, making it easier to understand and debug the contract's behavior.
Inter-Contract Calls with the SDK
This guide explains how to use the SDK to execute a contract call where one contract interacts with another contract. We will use a simple scenario involving a SimpleToken contract and a TokenDepositor contract.
SimpleToken and TokenDepositor Contracts
In this example, we have a SimpleToken contract representing a basic token contract capable of holding balances for different addresses. We also have a TokenDepositor contract that deposits tokens into the SimpleToken contract.
Contract: SimpleToken
Here's a simple token contract that allows holding balances:
<<< @/../../docs/sway/simple-token/src/main.sw#inter-contract-calls-1{rs:line-numbers}
Contract: TokenDepositor
The TokenDepositor contract imports the SimpleToken contract and calls its deposit function to deposit tokens:
<<< @/../../docs/sway/token-depositor/src/main.sw#inter-contract-calls-2{rs:line-numbers}
Inter-contract calls using the SDK
Once both contracts are deployed, we can use the SDK to make the TokenDepositor contract to call the SimpleToken contract.
<<< @./snippets/inter-contract-calls.ts#full{ts:line-numbers}
Pay attention to the method addContracts called by the TokenDepositor contract. This method accepts an array of instances of deployed contracts. Without calling this method, the inter-contract call will not work.
Multiple Contract Calls
You can execute multiple contract calls in a single transaction, either to the same contract or to different contracts. This can improve efficiency and reduce the overall transaction costs.
Same Contract Multi Calls
Use the multiCall method to call multiple functions on the same contract in a single transaction:
<<< @./snippets/multi-call/same-contract.ts#multicall-1{ts:line-numbers}
Different Contracts Multi Calls
The multiCall method also allows you to execute multiple contract calls to distinct contracts within a single transaction:
<<< @./snippets/multi-call/different-contracts.ts#multicall-2{ts:line-numbers}
You can also chain supported contract call methods, like callParams, for each contract call:
<<< @./snippets/multi-call/different-contracts-chain-methods.ts#multicall-3{ts:line-numbers}
When using multiCall, the contract calls are queued and executed only after invoking one of the following methods: .get, .simulate, or .call.
Using multiCall for Read-Only Contract Calls
When you need to read data from multiple contracts, the multiCall method can perform multiple read-only calls in a single transaction. This minimizes the number of requests sent to the network and consolidates data retrieval, making your dApp interactions more efficient.
<<< @./snippets/multi-call/different-contracts-readonly.ts#multicall-4{ts:line-numbers}
Making Calls with Different Wallets or Providers
This guide demonstrates how to make contract calls using different wallets and providers by passing either an Account or a Provider to the contract on instantiation.
Changing Wallets
To change the wallet associated with a contract instance, assign a new wallet to the instance's account property. This allows you to make contract calls with different wallets in a concise manner:
<<< @./snippets/utilities/using-different-wallet.ts#using-different-wallet{ts:line-numbers}
Changing Providers
Similarly, you can assign a custom provider to a contract instance by modifying its provider property. This enables you to use a provider wrapper of your choice:
const newProvider = new Provider(NEW_URL);
deployedContract.provider = newProvider;
Note: When connecting a different wallet to an existing contract instance, the provider used to deploy the contract takes precedence over the newly set provider. If you have two wallets connected to separate providers (each communicating with a different fuel-core instance), the provider assigned to the deploying wallet will be used for contract calls. This behavior is only relevant when multiple providers (i.e. fuel-core instances) are present and can be ignored otherwise.
Transferring assets
Consider a scenario where you're interacting with a smart contract and need to transfer assets to a recipient's wallet. The addTransfer enables you to combine these actions into a single transaction seamlessly.
The addTransfer method allows you to append an asset transfer to your contract call transaction. You can use it is shown in the following example:
<<< @./snippets/utilities/add-transfer-single-recipient.ts#add-transfer-1{ts:line-numbers}
In the previous example, we first use a contract call to the echo_u64 function. Following this, addTransfer is added to chain call to include a transfer of 100 units of the BaseAssetId in the transaction.
Batch Transfer
You can add a batch of transfers into a single transaction by using addBatchTransfer:
<<< @./snippets/utilities/add-transfer-multiple-recipients.ts#add-transfer-2{ts:line-numbers}
Deploying Contracts
To deploy a contract using the SDK, you can use the ContractFactory. This process involves collecting the contract artifacts, initializing the contract factory, and deploying the contract.
The SDK utilizes two different deployment processes, depending on the contract's size. The threshold for the contract size is dictated by the chain and can be queried:
<<< @./snippets/deploying-contracts/get-max-size.ts#full{ts:line-numbers}
It either uses a single create transaction to deploy the entire contract bytecode, or it splits the contract bytecode into multiple chunks, deploys them as blobs (on chain data accessible to the VM), and then generates a contract from the associated blob IDs. That generated contract is then deployed as a create transaction.
The ContractFactory offers the following methods for the different processes:
deployfor deploying contacts of any size (will automatically choose the appropriate deployment process).deployAsCreateTxfor deploying the entire contract bytecode in a single create transaction.deployAsBlobTxfor deploying the contract in chunks as blobs, and then deploying the contract as a create transaction.
Note: If the contract is deployed via blob deployments, multiple transactions will be required to deploy the contract.
Deploying a Contract Guide
This guide will cover the process of deploying a contract using the deploy method, however all these methods can be used interchangeably dependent on the contract size. In the guide we use a contract factory that has been built using Typegen. This tool provided by the Fuels CLI provides a better developer experience and end to end type support for your smart contracts.
1. Setup
After writing a contract in Sway you can build the necessary deployment artifacts either by running forc build (read more on how to work with Sway) or by using the Fuels CLI and running fuels build using your chosen package manager. We recommend using the Fuels CLI as it provides a more comprehensive usage including end to end type support.
Once you have the contract artifacts, it can be passed to the ContractFactory for deployment, like so:
<<< @./snippets/deploying-contracts/deployment.ts#setup{ts:line-numbers}
2. Contract Deployment
As mentioned earlier, there are two different processes for contract deployment handled by the ContractFactory. These can be used interchangeably, however, the deploy method is recommended as it will automatically choose the appropriate deployment process based on the contract size.
This call resolves as soon as the transaction to deploy the contract is submitted and returns three items: the contractId, a waitForTransactionId function and a waitForResult function.
<<< @./snippets/deploying-contracts/deployment.ts#deploy{ts:line-numbers}
The contract instance will be returned only after calling waitForResult and waiting for it to resolve. To avoid blocking the rest of your code, you can attach this promise to a hook or listener that will use the contract only after it is fully deployed. Similarly, the transaction ID is only available once the underlying transaction has been funded. To avoid blocking the code until the ID is ready, you can use the waitForTransactionId function to await it's retrieval.
3. Executing a Contract Call
Now that the contract is deployed, you can interact with it by submitting a contract call:
<<< @./snippets/deploying-contracts/deployment.ts#call{ts:line-numbers}
Deploying a Large Contract as Blobs
In the above guide we use the recommended deploy method. If you are working with a contract that is too large to be deployed in a single transaction, then the SDK will chunk the contract for you and submit it as blobs, to then be accessed later by a create transaction. This process is handled by the ContractFactory.deployAsBlobTx method.
<<< @./snippets/deploying-contracts/deployment.ts#blobs{ts:line-numbers}
In the above example, we also pass a chunkSizeMultiplier option to the deployment method. The SDK will attempt to chunk the contract to the most optimal about, however the transaction size can fluctuate and you can also be limited by request size limits against the node. By default we set a multiplier of 0.95, meaning the chunk size will be 95% of the potential maximum size, however you can adjust this to suit your needs and ensure the transaction passes. It must be set to a value between 0 and 1.
Note: Deploying large contracts using blob transactions will take more time. Each transaction is dependent and has to wait for a block to be produced before it gets mined. Then a create transaction is submitted as normal. So you will need to wait longer than usual for the contract to be fully deployed and can be interacted with.
Storage Slots
When deploying a contract, you can specify the custom storage slots that you want to use.
<<< @./snippets/storage-slots/override-storage-slots.ts#contract-deployment-storage-slots{ts:line-numbers}
Using plain JavaScript
In the above example, we directly imported the storage slots from a JSON file generated by the Sway compiler.
Instead of importing from a file, you can also specify the custom storage slots directly in your code:
<<< @./snippets/storage-slots/override-storage-slots-inline.ts#contract-deployment-storage-slots-inline{ts:line-numbers}
Auto-load of Storage Slots
Code generated using Typegen automatically load Storage Slots for you.
Configurable Constants
Sway introduces a powerful feature: configurable constants. When creating a contract, you can define constants, each assigned with a default value.
Before deploying the contract, you can then redefine the value for these constants, it can be all of them or as many as you need.
This feature provides flexibility for dynamic contract environments. It allows a high level of customization, leading to more efficient and adaptable smart contracts.
Defining Configurable Constants
Below is an example of a contract in which we declare four configurable constants:
<<< @/../../docs/sway/echo-configurables/src/main.sw#configurable-constants-1{rust:line-numbers}
In this contract, the function echo_configurables returns the values of the configurable constants, which we'll use for demonstrating the setting of configurables via the SDK.
Setting New Values For Configurable Constants
During contract deployment, you can define new values for any/all of the configurable constants. The example below shows setting of one configurable constant, while the others will have default values.
<<< @./snippets/configurable-constants.ts#setting-configurable-constant{ts:line-numbers}
Please note that when assigning new values for a Struct, all properties of the Struct must be defined. Failing to do so will result in an error:
<<< @./snippets/configurable-constants.ts#invalid-configurable{ts:line-numbers}
Minted Token Asset ID
The asset ID of a token on the Fuel network is determined by two factors:
- The ID of the contract that minted the token,
- A sub-identifier (Sub ID)
Both of which are B256 strings.
The process involves applying a SHA-256 hash algorithm to the combination of the Contract ID and the Sub ID, to derive an Asset ID - as explained here.
Consider the following simplified token contract:
<<< @/../../docs/sway/token/src/main.sw#minted-token-asset-id-1{rs:line-numbers}
Imagine that this contract is already deployed and we are about to mint some coins:
<<< @./snippets/utilities/minted-token-asset-id.ts#minted-token-asset-id-2{ts:line-numbers}
Obtaining the Asset ID
Since the asset ID depends on the contract ID, which is always dynamic (unlike the sub ID, which can be set to a fixed value), the helper getMintedAssetId can be used to easily obtain the asset ID for a given contract ID and sub ID.
Create Asset Id
The SDK provides a helper named createAssetId which takes the contract ID and sub ID as parameters. This helper internally calls getMintedAssetId and returns the Sway native parameter AssetId, ready to be used in a Sway program invocation:
<<< @./snippets/utilities/create-asset-id.ts#create-asset-id-1{ts:line-numbers}
Managing Deployed Contracts
To interact with a deployed contract using the SDK without redeploying it, you only need the contract ID and its JSON ABI. This allows you to bypass the deployment setup.
Contract ID
The contractId property from the Contract class is an instance of the Address class.
The Address class also provides a set of utility functions for easy manipulation and conversion between address formats along with one property; b256Address, which is a string encoded in B256 format.
When you log the contractId property of an instantiated Contract using console.log, the output appears as follows:
Address {
b256Address: '0xcd16d97c5c4e18ee2e8d6428447dd9c8763cb0336718b53652d049f8ec88b3ba'
}
If you have already an instantiated and deployed contract in hands you can create another contract instance simply by using the contractId property and the contract JSON ABI:
<<< @./snippets/managing-deployed-contracts.ts#with-contractId{ts:line-numbers}
The previous example assumes that you have a Contract instance at hand. However, some Fuel tools and Sway use the B256 type format, a hex-encoded string-like type, for contract IDs.
You might have this format instead, for example, if you have deployed your contract with forc deploy.
The process of instantiating a Contract remains the same when using a contract ID of type B256:
<<< @./snippets/managing-deployed-contracts.ts#with-b256{ts:line-numbers}
Proxy Contracts
Automatic deployment of proxy contracts can be enabled in Forc.toml.
We recommend that you use fuels deploy to deploy and upgrade your contract using a proxy as it will take care of everything for you. However, if you want to deploy a proxy contract manually, you can follow the guide below.
Manually Deploying and Upgrading by Proxy
As mentioned above, we recommend using fuels deploy to deploy and upgrade your contract because it will handle everything automatically. However, the guide below will explain this process in detail if you want to implement it yourself.
We recommend using the SRC14 compliant owned proxy contract as the underlying proxy as that is the one we will use in this guide and the one used by fuels deploy. A TypeScript implementation of this proxy is exported from the fuels package as Src14OwnedProxy and Src14OwnedProxyFactory.
The overall process is as follows:
- Deploy your contract
- Deploy the proxy contract
- Set the target of the proxy contract to your deployed contract
- Make calls to the contract via the proxy contract ID
- Upgrade the contract by deploying a new version of the contract and updating the target of the proxy contract
Note: When new storage slots are added to the contract, they must be initialized in the proxy contract before they can be read from. This can be done by first writing to the new storage slot in the proxy contract. Failure to do so will result in the transaction being reverted.
For example, lets imagine we want to deploy the following counter contract:
<<< @/../../docs/sway/counter/src/main.sw#proxy-1{rs:line-numbers}
Let's deploy and interact with it by proxy. First let's setup the environment and deploy the counter contract:
<<< @./snippets/proxy-contracts.ts#proxy-2{ts:line-numbers}
Now let's deploy the SRC14 compliant proxy contract and initialize it by setting its target to the counter target ID.
<<< @./snippets/proxy-contracts.ts#proxy-3{ts:line-numbers}
Finally, we can call our counter contract using the contract ID of the proxy.
<<< @./snippets/proxy-contracts.ts#proxy-4{ts:line-numbers}
Now let's make some changes to our initial counter contract by adding an additional storage slot to track the number of increments and a new get method that retrieves its value:
<<< @/../../docs/sway/counter-v2/src/main.sw#proxy-5{rs:line-numbers}
We can deploy it and update the target of the proxy like so:
<<< @./snippets/proxy-contracts.ts#proxy-6{ts:line-numbers}
Then, we can instantiate our upgraded contract via the same proxy contract ID:
<<< @./snippets/proxy-contracts.ts#proxy-7{ts:line-numbers}
For more info, please check these docs:
- Proxy Contracts
- Sway Libs / Upgradability Library
- Sway Standards / SRC-14 - Simple Upgradable Proxies
Understanding the FuelVM Binary File
When you compile your Sway code using the forc build command, it generates a bytecode file. This binary file contains the compiled code that the Fuel Virtual Machine (FuelVM) will interpret and execute.
For example, consider the following smart contract:
<<< @/../../docs/sway/echo-values/src/main.sw#understanding-fuel-binary-file{ts:line-numbers}
After running forc build, a binary file will be generated with the following content:
$ cat out/debug/echo-values.bin
�GT]����]@`I]G�I@sH]G�I@sHr�{6�]D`J]C�%E]@`J$@Ͼ{RD�^�%
At first glance, the content appears unreadable. However, forc provides a helpful interpreter for this bytecode: the forc parse-bytecode command. This command takes the binary data and outputs the equivalent FuelVM assembly:
$ forc parse-bytecode out/debug/echo-values.bin
half-word byte op raw notes
0 0 JI(4) 90 00 00 04 jump to byte 16
1 4 NOOP 47 00 00 00
2 8 Undefined 00 00 00 00 data section offset lo (0)
3 12 Undefined 00 00 00 34 data section offset hi (52)
4 16 LW(63, 12, 1) 5d fc c0 01
5 20 ADD(63, 63, 12) 10 ff f3 00
6 24 LW(17, 6, 73) 5d 44 60 49
7 28 LW(16, 63, 1) 5d 43 f0 01
8 32 EQ(16, 17, 16) 13 41 14 00
9 36 JNZI(16, 11) 73 40 00 0b conditionally jump to byte 44
10 40 RVRT(0) 36 00 00 00
11 44 LW(16, 63, 0) 5d 43 f0 00
12 48 RET(16) 24 40 00 00
13 52 Undefined 00 00 00 00
14 56 Undefined 00 00 00 01
15 60 Undefined 00 00 00 00
16 64 XOR(20, 27, 53) 21 51 bd 4b
When deploying your smart contract using the SDK, the binary file plays a crucial role. It is sent to the FuelVM in a transaction, allowing the FuelVM to interpret and execute your smart contract.
Scripts
A script, in Sway, is runnable bytecode on the chain which executes once to perform some task. A script can return a single value of any type.
Learn more about scripts here.
Instantiating a script
Similar to contracts and predicates, once you've written a script in Sway and compiled it with forc build (read here for more on how to work with Sway), you'll get the script binary. Using the binary, you can instantiate a script as shown in the code snippet below:
<<< @./snippets/initialising-scripts.ts#script-init{ts:line-numbers}
In the next section, we show how to run a script.
Deploying Scripts
In order to optimize the cost of your recurring script executions, we recommend first deploying your script. This can be done using the Fuels CLI and running the deploy command.
By deploying the script, its bytecode is stored on chain as a blob. The SDK will then produce bytecode that can load the blob on demand to execute the original script. This far reduces the repeat execution cost of the script.
How to Deploy a Script
To deploy a script, we can use the Fuels CLI and execute the deploy command.
This will perform the following actions:
- Compile the script using your
forcversion - Deploy the built script binary to the chain as a blob
- Generate a script that loads the blob that can be used to execute the script
- Generate types for both the script and the loader that you can use in your application
We can then utilize the above generated types like so:
<<< @./snippets/deploying-scripts.ts#deploying-scripts{ts:line-numbers}
Script With Configurable
In the same way as contracts and predicates, Scripts also support configurable constants. This feature enables dynamic adjustment of certain values within your scripts.
Configurable constants are fairly straightforward to add and set in your scripts.
Let's consider the following script:
<<< @/../../docs/sway/script-sum/src/main.sw#script-with-configurable-contants-1{rust:line-numbers}
In this script, AMOUNT is a configurable constant with a default value of 10. The main function returns the sum of the inputted_amount and the configurable constant AMOUNT.
To change the value of the AMOUNT constant, we can use the setConfigurableConstants method as shown in the following example:
<<< @./snippets/script-with-configurable.ts#script-with-configurable-contants-2{ts:line-numbers}
In this example, we're setting a new value 81 for the AMOUNT constant. We then call the main function with an inputted value of 10.
The expectation is that the script will return the sum of the inputted value and the new value of AMOUNT.
This way, configurable constants in scripts allow for more flexibility and dynamic behavior during execution.
Full Example
For a full example, see below:
<<< @./snippets/script-with-configurable.ts#full{ts:line-numbers}
Running a script
Suppose your Sway script main function is written using the arguments passed to the main function like so:
<<< @/../../docs/sway/script-main-args/src/main.sw#script-with-main-args{rust:line-numbers}
You can still hand code out a solution wrapper using callScript utility to call your script with data. However, if you prefer to use the ABI generated from your script, you can use the ScriptFactory helper:
<<< @./snippets/script-with-main-args.ts#full{ts:line-numbers}
Preparing a Script Transaction
Akin to Contracts, we can configure the call parameters and transaction parameters for Scripts, as well as retrieve the entire transaction request or transaction ID prior to submission.
<<< @./snippets/script-with-configurable.ts#preparing-scripts{ts:line-numbers}
Predicates
Predicates in Sway are specific types of programs that return a boolean value, meaning they function like rules that a transaction must follow to be valid.
They don't have access to the information written on the blockchain – they make decisions based solely on the received parameters.
These predicates are pure functions, which means they don't cause any unintended side effects.
The key difference here is that instead of checking these rules directly on the blockchain, we check them 'off' the blockchain first. Once we're confident they're valid, we then record the transaction on the blockchain.
This method is not only more efficient but also helps to prevent traffic jams on the network and makes transactions cheaper. It does so by reducing the need for repetitive calculations on the blockchain.
Working with Predicates
Users can send assets to the predicate address as they would to any other address on the blockchain. To spend funds stored at the predicate address, users must provide the original byte code of the predicate and, if required, the predicate data.
The predicate data relates to the parameters received by the predicate's main function. This data comes into play during the byte code's execution. If the main function does not have any parameters then there is no data to be provided, therefore we do not provide the predicate data.
If the predicate is validated successfully, the funds will be accessible. Otherwise, the SDK will throw a validation error.
In the next section, we provide a step-by-step guide on how to interact with a predicate to validate your transactions.
Debugging Predicates
Currently there is no way to debug a predicate yet. In the meantime, a practical workaround is to initially write, test, and debug your predicate as a script, which has more debugging tools available. Once it's working as expected, you can then convert it back into a predicate.
Instantiating predicates
A predicate in Sway can be as simple as the following:
<<< @/../../docs/sway/return-true-predicate/src/main.sw#predicate-simple-1{rust:line-numbers}
In this minimal example, the main function does not accept any parameters and simply returns true.
Just like contracts in Sway, once you've created a predicate, you can compile it using forc build. For more information on working with Sway, refer to the Sway documentation.
After compiling, you will obtain the binary of the predicate and its JSON ABI (Application Binary Interface). Using these, you can instantiate a predicate in TypeScript as shown in the code snippet below:
<<< @./snippets/instantiation/simple.ts#predicate-simple-2{ts:line-numbers}
The created Predicate instance, among other things, has three important properties: the predicate bytes (byte code), the chainId, and the predicate address.
This address, generated from the byte code, corresponds to the Pay-to-Script-Hash (P2SH) address used in Bitcoin.
Predicate with multiple arguments
You can pass more than one argument to a predicate. For example, this is a predicate that evaluates to true if the two arguments are not equal:
<<< @/../../docs/sway/predicate-multi-args/src/main.sw#predicate-multi-args-1{rust:line-numbers}
You can pass the two arguments to this predicate like this:
<<< @./snippets/instantiation/multi-args.ts#predicate-multi-args-2{rust:line-numbers}
Predicate with a Struct argument
You can also pass a struct as an argument to a predicate. This is one such predicate that expects a struct as an argument:
<<< @/../../docs/sway/predicate-main-args-struct/src/main.sw#predicate-main-args-struct-1{rust:line-numbers}
You can pass a struct as an argument to this predicate like this:
<<< @./snippets/instantiation/struct-arg.ts#predicate-main-args-struct-2{ts:line-numbers}
Deploying Predicates
In order to optimize the cost of your recurring predicate executions, we recommend first deploying your predicate. This can be done using the Fuels CLI and running the deploy command.
By deploying the predicate, its bytecode is stored on chain as a blob. The SDK will then produce bytecode that can load the blob on demand to execute the original predicate. This far reduces the repeat execution cost of the predicate.
How to Deploy a Predicate
To deploy a predicate, we can use the Fuels CLI and execute the deploy command.
This will perform the following actions:
- Compile the predicate using your
forcversion - Deploy the built predicate binary to the chain as a blob
- Generate a new, smaller predicate that loads the deployed predicate's blob
- Generate types for both the predicate and the loader that you can use in your application
We can then utilize the above generated types like so:
<<< @./snippets/deploying-predicates.ts#full{ts:line-numbers}
Predicate With Configurable Constants
Predicates, much like contracts and scripts, also supports configurable constants. This enables Predicates to suit specific use cases and enhance their functionality.
Example: Asset Transfer Validation
Let's consider an example where a predicate is used to validate an asset transfer. In this case, the transfer will only be executed if the recipient's address is on a pre-approved whitelist.
The following snippet illustrates how this could be implemented:
<<< @/../../docs/sway/whitelisted-address-predicate/src/main.sw#full{rust:line-numbers}
In this example, you'll notice the use of a configurable constant named WHITELISTED. This constant has a default value that represents the default approved address.
Modifying The Whitelist
If there is a need to whitelist another address, the WHITELISTED constant can be easily updated. The following snippet demonstrates how to set a new value for the WHITELISTED constant and to make the predicate execute the transfer:
<<< @./snippets/configurables/configurable-set-data.ts#full{ts:line-numbers}
By ensuring that the updated WHITELISTED address matches the intended recipient's address, the predicate will validate the transfer successfully.
Default Whitelist Address
In scenarios where the default whitelisted address is already the intended recipient, there's no need to update the WHITELISTED constant. The predicate will validate the transfer based on the default value. Here's how this scenario might look:
<<< @./snippets/configurables/configurable-default.ts#full{ts:line-numbers}
This ability to configure constants within predicates provides a flexible mechanism for customizing their behavior, thereby enhancing the robustness and versatility of our asset transfer process.
It's important to note that these customizations do not directly modify the original predicate. The address of a predicate is a hash of its bytecode. Any change to the bytecode, including altering a constant value, would generate a different bytecode, and thus a different hash. This leads to the creation of a new predicate with a new address.
This doesn't mean that we're changing the behavior of the original predicate. Instead, we're creating a new predicate with a different configuration.
Therefore, while configurable constants do indeed enhance the flexibility and robustness of predicates, it is achieved by creating new predicates with different configurations, rather than altering the behavior of existing ones.
Send And Spend Funds From Predicates
Predicates can be used to validate transactions. This implies that a predicate can safeguard assets, only allowing their transfer if the predicate conditions are met.
This guide will demonstrate how to send and spend funds using a predicate.
Predicate Example
Consider the following predicate:
<<< @/../../docs/sway/simple-predicate/src/main.sw#send-and-spend-funds-from-predicates-1{rust:line-numbers}
This predicate accepts an address of type B256 and compares it with a hard-coded address of the same type. If both addresses are equal, the predicate returns true, otherwise it will return false.
Interacting with the Predicate Using SDK
Let's use the above predicate to validate our transaction.
Once you've compiled the predicate (forc build), you'll obtain two important artifacts: the JSON ABI and the predicate's binary code. These are needed to instantiate a new predicate.
This is where we also pass in the predicate's data. Note that the main function in our predicate example requires a parameter called input_address of type B256. We will pass this parameter to the Predicate constructor along with the bytecode and the JSON ABI.
<<< @./snippets/cookbook/transferring-assets.ts#send-and-spend-funds-from-predicates-2{ts:line-numbers}
Note: If you want to pass in the predicate data after instantiating the
Predicateor if you want to use a different data than the one passed in the constructor, you will have to create a newPredicateinstance.
With the predicate instantiated, we can transfer funds to its address. This requires us to have a wallet with sufficient funds. If you're unsure about using wallets with the SDK, we recommend checking out our wallet guide.
<<< @./snippets/cookbook/transferring-assets.ts#send-and-spend-funds-from-predicates-3{ts:line-numbers}
Now that our predicate holds funds, we can use it to validate a transaction and hence execute our transfer. We can achieve that by doing the following:
<<< @./snippets/cookbook/transferring-assets.ts#send-and-spend-funds-from-predicates-5{ts:line-numbers}
Note the method transfer has two parameters: the recipient's address and the intended transfer amount.
Once the predicate resolves with a return value true based on its predefined condition, our predicate successfully spends its funds by means of a transfer to a desired wallet.
In a similar approach, you can use the createTransfer method, which returns a ScriptTransactionRequest. Then, we can submit this transaction request by calling the sendTransaction method.
The following example, we are pre-staging a transaction and therefore we are able to know the transaction ID without actually submitting the transaction.
<<< @./snippets/cookbook/pre-stage.ts#send-and-spend-funds-from-predicates-8{ts:line-numbers}
Spending Entire Predicate Held Amount
Trying to forward the entire amount held by the predicate results in an error because no funds are left to cover the transaction fees. Attempting this will result in an error message like:
<<< @./snippets/cookbook/failure-not-enough-funds.ts#send-and-spend-funds-from-predicates-6{ts:line-numbers}
Predicate Validation Failure
What happens when a predicate fails to validate? Recall our predicate only validates if the input_address matches the hard-coded valid_address. Hence, if we set a different data from the valid_address, the predicate will fail to validate.
When a predicate fails to validate, the SDK throws an error that starts like this:
<<< @./snippets/cookbook/failure-returns-false.ts#send-and-spend-funds-from-predicates-7{ts:line-numbers}
Interacting With Predicates
The Predicate class extends the Account class, inheriting all its methods. Therefore, there are multiple ways to interact with predicates, but broadly speaking, we can think about three:
Checking BalancesTransactionsTransfers
Checking Balances
getBalances
This will return the balances of all assets owned by the predicate.
See also: Checking Wallet Balances
getResourcesToSpend
This will return the resources owned by a predicate so that they can be added to a transaction request.
This method is called under the hood when using transfer or createTransfer.
You may want to use this method when using a predicate in an existing transaction request.
<<< @./snippets/methods/get-resources-to-spend.ts#getResourcesToSpend{ts:line-numbers}
Transactions
sendTransaction
This is used to send a transaction to the node.
<<< @./snippets/methods/send-transaction.ts#sendTransaction{ts:line-numbers}
simulateTransaction
You can use the simulateTransaction method to dry-run a predicate call without consuming resources. A typical use case of a dry-run call is to validate that sufficient funds are available to cover the transaction fees.
<<< @./snippets/methods/simulate-transaction.ts#simulateTransaction{ts:line-numbers}
Transfers
createTransfer
The createTransfer method creates a transaction request with all the necessary transfer details. It automatically estimates the transaction costs via a dry-run call and funds the request with the required predicate resources. After this, one can submit the returned transaction request with greater certainty that it will succeed.
However, please remember that you can still modify the transfer request details and use its properties before submitting it to the node.
<<< @./snippets/methods/create-transfer.ts#createTransfer{ts:line-numbers}
transfer
You can send funds to another address using the transfer method.
<<< @./snippets/methods/transfer.ts#transfer{ts:line-numbers}
Custom Transactions
Utilizing predicate logic unlocks a wide range of possibilities for your dApps when creating transactions. Therefore, pairing predicates with custom transactions can help you achieve more complex use cases. This can be achieved by instantiating a custom transaction, appending the predicate resources, and submitting the transaction via a successfully validated predicate.
Custom transactions can be shaped via a ScriptTransactionRequest instance. For more information on crafting custom transactions and the methods available to them, please refer to the Transaction Request guide.
However, this guide will demonstrate how to use a predicate in a custom transaction. Consider the following predicate, where a configurable pin must be used to validate the predicate and unlock the funds:
<<< @/../../docs/sway/configurable-pin/src/main.sw#full{rust:line-numbers}
We can interact with the above and include it in a custom transaction like so:
<<< @./snippets/custom-transactions.ts#predicate-custom-transaction{ts:line-numbers}
Transactions
A transaction is a way of interacting with a Fuel blockchain and can include actions like transferring assets, deploying contracts and minting tokens. All of which are possible through the SDK by using simple utility methods or building out more custom transactions.
Transferring assets is the most common transaction type and can be be executed by calling the transfer function from an account to a recipient address:
<<< @./snippets/transaction.ts#transactions-1{ts:line-numbers}
Deploying and interacting with contracts are other common transactions. More information on this can be found in the contracts guide, either through the contract deployment guide or the contract interaction guide.
This guide will discuss how to create and modify transactions to fit bespoke use cases, as well as submit them to the network using transactional policies and parameters. As well as retrieving information about submitted transactions.
Transaction Request
A transaction request provides the foundations for submitting a transaction and interacting with the blockchain.
Within Fuel, we have the following transaction types:
- Script
- Create
- Mint
The SDK provides class helpers for handling script and create transactions: ScriptTransactionRequest and CreateTransactionRequest, respectively.
Note: Mint transactions can only be created by the block producer and do not have any use outside of block creation. Therefore, the SDK only provides the ability to decode them.
Creating a Transaction Request
To create a transaction request, you must first instantiate either a ScriptTransactionRequest or CreateTransactionRequest.
A ScriptTransactionRequest is used for script transactions, which allows you to execute bytecode on chain to perform a task or chain of tasks. Within the SDK they can be created like so:
<<< @./snippets/transaction-request/create-request.ts#transaction-request-1{ts:line-numbers}
A CreateTransactionRequest is used for create transactions, which are transactions that create a new contract on the blockchain.
<<< @./snippets/transaction-request/create-request.ts#transaction-request-2{ts:line-numbers}
Note: We recommend you use the
ContractFactoryfor contract deployment as this will shape the create transaction request for you. Information on this can be found in the contract deployment guide.
Modifying a Transaction Request
Once you have instantiated a transaction request, you can modify it by setting the transaction parameters and policies. This can either be done manually by directly altering the transaction request object, or through helper methods that are available on the above classes.
Adding OutputCoin
Including OutputCoins in the transaction request specifies the UTXOs that will be created once the transaction is processed. These UTXOs represent the amounts being transferred to specified account addresses during the transaction:
<<< @./snippets/transaction-request/add-output-coin.ts#transaction-request-3{ts:line-numbers}
Estimating and Funding the Transaction Request
Before submitting a transaction, it is essential to ensure it is properly funded to meet its requirements and cover the associated fee. The SDK offers two approaches for this, one is to use the estimateAndFund helper:
<<< @./snippets/transaction-request/estimate-and-fund.ts#estimate-and-fund{ts:line-numbers}
This approach provides a simple one-liner for estimating and funding the transaction request. Ensuring that the gasLimit and maxFee are accurately calculated and that the required amounts for OutputCoins are fulfilled, as well as fetching and adding any missing resources from the calling account.
The other more manual approach is as so:
<<< @./snippets/transaction-request/get-transaction-cost.ts#transaction-request-4{ts:line-numbers}
This approach provides the same behaviour as the estimateAndFund helper, but gives more granular control over the transaction request. The getTransactionCost method also returns various information about the simulated request that you may want to use to further modify the transaction request, more on that can be found in the API reference.
Manually Fetching Resources
In certain scenarios, you may need to manually fetch resources. This can be achieved using the getResourcesToSpend method, which accepts an array of CoinQuantities and returns the necessary resources to meet the specified amounts:
<<< @./snippets/transaction-request/fetch-resources.ts#transaction-request-5{ts:line-numbers}
Manually Fetching Coins or Messages
If needed, you can manually include specific coins or messages in the transaction. However, this approach is generally discouraged and should only be used in scenarios where explicitly adding particular coins or messages to the transaction request is required:
<<< @./snippets/transaction-request/fetch-coins.ts#transaction-request-6{ts:line-numbers}
Adding a Contract Input and Output to a Transaction Request
Imagine that you have a Sway script that manually calls a contract:
<<< @/../../docs/sway/script-call-contract/src/main.sw#transaction-request-7{rs:line-numbers}
In those cases, you will need to add both an InputContract and OutputContract to the transaction request:
<<< @./snippets/transaction-request/input-contract.ts#transaction-request-8{ts:line-numbers}
Adding a Predicate to a Transaction Request
Predicates are used to define the conditions under which a transaction can be executed. Therefore you may want to add a predicate to a transaction request to unlock funds that are utilized by a script. This can be added like so:
<<< @./snippets/transaction-request/add-predicate.ts#transaction-request-9{ts:line-numbers}
Note: For more information on predicates, including information on configuring them, funding them and using them to unlock funds, please refer to the predicate guide.
Adding a Witness and Signing a Transaction Request
The SDK provides a way of either modifying the witnesses for a transaction request directly, or by passing accounts. This will then sign the transaction request with the account's private key. Below will detail how to add a witness to a transaction request:
<<< @./snippets/transaction-request/add-witness.ts#transaction-request-10{ts:line-numbers}
A more complex example of adding multiple witnesses to a transaction request can be seen in the multiple signers guide here, which validates the signatures inside the script itself.
Note: Once
addAccountWitnesseshas been called, any additional modifications to the transaction request will invalidate the signature as the transaction ID changes. Therefore, it is recommended to add witnesses last.
Getting the Transaction ID for a Transaction Request
The transaction ID is a SHA-256 hash of the entire transaction request. This can be useful for tracking the transaction on chain. To get the transaction ID, you can use the following method:
<<< @./snippets/transaction-request/add-witness.ts#transaction-request-11{ts:line-numbers}
Note: Any changes made to a transaction request will alter the transaction ID. Therefore, you should only get the transaction ID after all modifications have been made.
Burning assets
Assets can be burnt as part of a transaction that has inputs without associated output change. The SDK validates against this behavior, so we need to explicitly enable this by sending the transaction with the enableAssetBurn option set to true.
<<< @./snippets/transaction-request/asset-burn.ts#asset-burn{ts:line-numbers}
Note: Burning assets is permanent and all assets burnt will be lost. Therefore, be mindful of the usage of this functionality.
Adding Parameters
Transaction parameters allow you to configure various aspects of your blockchain transactions. Dependent on these parameters, it may introduce a transaction policy.
All available parameters are shown below:
<<< @./snippets/transaction-parameters.ts#transaction-parameters-7{ts:line-numbers}
Gas Limit
The maximum amount of gas you're willing to allow the transaction to consume. If the transaction requires more gas than this limit, it will fail.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-1{ts:line-numbers}
Max Fee
The maximum amount you're willing to pay for the transaction using the base asset. This allows users to set an upper limit on the transaction fee they are willing to pay, preventing unexpected high costs due to sudden network congestion or fee spikes.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-2{ts:line-numbers}
Tip
An optional amount of the base asset to incentivise the block producer to include the transaction, ensuring faster processing for those willing to pay more. The value set here will be added to the transaction maxFee.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-3{ts:line-numbers}
Maturity
The number of blocks that must pass before the transaction can be included in a block. This is useful for time-sensitive transactions, such as those involving time-locked assets.
For example, if the chain produces a new block every second, setting Maturity to 10 means the transaction will be processed after approximately 10 seconds.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-4{ts:line-numbers}
Witness Limit
The maximum byte length allowed for the transaction witnesses array. For instance, imagine a transaction that will deploy a contract. The contract bytecode will be one of the entries in the transaction witnesses. If you set this limit to 5000 and the contract bytecode length is 6000, the transaction will be rejected because the witnesses bytes length exceeds the maximum value set.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-5{ts:line-numbers}
Expiration
The block number after which the transaction can no longer be included in the blockchain. For example, if you set the expiration block for your transaction to 200, and the transaction remains in the queue waiting to be processed when block 200 is created, the transaction will be rejected.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-6{ts:line-numbers}
Variable Outputs
The number of variable outputs that should be added to the transaction request. You can read more about it on this guide
Note: Setting transaction parameters is optional. If you don't specify them, the SDK will fetch some sensible defaults from the chain.
Setting Transaction Parameters
To set the transaction parameters, you have access to the txParams method on a transaction request.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-8{ts:line-numbers}
The same method is also accessible within a function invocation scope, so it can also be used when calling contract functions.
<<< @./snippets/transaction-parameters.ts#transaction-parameters-9{ts:line-numbers}
Note: When performing an action that results in a transaction (e.g. contract deployment, contract call with
.call(), asset transfer), the SDK will automatically estimate the fee based on the gas limit and the transaction's byte size. This estimation is used when building the transaction. As a side effect, your wallet must own at least one coin of the base asset, regardless of the amount.
Full Example
Here is the full example of the transaction parameters:
<<< @./snippets/transaction-parameters.ts#full{ts:line-numbers}
Adding Policies
Transaction policies are rules that can govern how a transaction is processed, introduced by the transaction parameters that you pass to a transaction request. The available policies are as follows:
Tip
Optional amount on the base asset to incentivise block producer to include transaction, ensuring faster processing for those willing to pay more. The value set here will be added to the transaction maxFee.
Witness Limit
The maximum byte length allowed for the transaction witnesses array.
Maturity
The number of blocks that must pass before the transaction can be included in a block.
Max Fee
The maximum amount you're willing to pay for the transaction using the base asset.
Expiration
Block number after which the transaction can no longer be included in the blockchain.
Setting Transaction Policies
The following snippet shows which transaction parameters correspond to which policies:
<<< @./snippets/transaction-policies/setting-policies.ts#transaction-policies-1{ts:line-numbers}
Retrieving Transaction Policies from a Transaction
Policies used for a transaction can be retrieved from a transaction using a TransactionResponse. The below snippet will show how to retrieve the policies from a transaction:
<<< @./snippets/transaction-policies/policies-from-response.ts#transaction-policies-2{ts:line-numbers}
Transaction Response
Once a transaction has been submitted, you may want to extract information regarding the result of the transaction. The SDK offers a TransactionResponse class with helper methods to expose the following information:
- The transaction ID
- The status (submitted, success, squeezed out, or failure)
- Receipts (return data, logs, mints/burns, transfers and panic/reverts)
- Operations (contract calls, transfers, withdrawals)
- Gas fees and usages
- Date and time of the transaction
- The block the transaction was included in
We can easily extract this information from a contract call:
<<< @./snippets/transaction-response/contract-call.ts#transaction-response-1{ts:line-numbers}
We can also use the result of a transaction request to extract a transaction summary:
<<< @./snippets/transaction-response/from-submitted-request.ts#transaction-response-2{ts:line-numbers}
Or we can build a transaction summary from a stored transaction ID:
<<< @./snippets/transaction-response/from-submitted-request.ts#transaction-response-3{ts:line-numbers}
Optimizing Frontend Apps
Your application must perform a series of operations to estimate, submit and receive the result of a transaction. However, the flow in which it performs these actions can be organized or performed optimistically, increasing it's perceived speed.
Use Case
In a frontend application, imagine we have a button that executes a contract call:
<Button onClick={handleSubmit}>Submit</Button>
The handler would be implemented as follows:
<<< @./snippets/transaction-speed/transaction-speed-init.ts#main{ts:line-numbers}
Once the user clicks the button, multiple sequential calls are made to the network, which can take a while because the transaction must be:
- Estimated
- Funded
- Submitted
Optimization Strategy
With a few optimizations, the flow can be organized as follows:
<<< @./snippets/transaction-speed/transaction-speed-optimized.ts#main{ts:line-numbers}
Conclusion
Finally, when users click the button, they only need to submit the transaction, which vastly improves the perceived speed of the transaction because many of the necessary requests were done upfront, under the hood.
Just remember:
- After preparation, any changes made to the transaction request will require it to be re-estimated and re-funded before it can be signed and submitted.
See Also
- Check a full example at Optimized React Example
Encoding
Encoding is the process of serializing data into a format that is suitable for transmission or storage. This is important for blockchains as it enables state minimization and efficiency, as well as for generating proofs.
Fortunately, if you are working with program types on the Fuel network such as calling contracts, the SDK will handle encoding for you automatically. It will adhere to the argument encoding specification, so you can work with data in its expected format rather than as bytecode.
However, there may be scenarios where you want to manipulate call or return data yourself or even implement your own serialization specification. This guide will cover how to encode and decode data using the SDK.
Encode and Decode
To interact with the FuelVM, types must be encoded and decoded per the argument encoding specification. The SDK provides the Interface class to encode and decode data.
The relevant methods of Interface are:
encodeTypedecodeType
The Interface class requires you to pass the ABI on initialization. Both methods accept a concreteTypeId, which must exist in the ABI's concreteTypes array. After that, a suitable coder will be assigned to encode/decode that type.
Imagine we are working with the following script that returns the sum of two u32 integers:
<<< @/../../docs/sway/script-sum/src/main.sw#encode-and-decode-1{rust:line-numbers}
When you build this script, using:
forc build
It will produce the following ABI:
<<< @./snippets/encode-and-decode.jsonc#encode-and-decode-2{json:line-numbers}
Now, let's prepare some data to pass to the main function to retrieve the combined integer. The function expects and returns a u32 integer. So here, we will encode the u32 to pass it to the function and receive the same u32 back, as bytes, that we'll use for decoding. We can do both of these with the Interface.
First, let's prepare the transaction:
<<< @./snippets/encode-and-decode.ts#encode-and-decode-3{ts:line-numbers}
Now, we can encode the script data to use in the transaction:
<<< @./snippets/encode-and-decode.ts#encode-and-decode-4{ts:line-numbers}
Finally, we can decode the result:
<<< @./snippets/encode-and-decode.ts#encode-and-decode-5{ts:line-numbers}
A similar approach can be taken with Predicates; however, you must set the encoded values to the predicateData property.
Contracts require more care. Although you can utilize the scriptData property, the arguments must be encoded as part of the contract call script. Therefore, it is recommended to use a FunctionInvocationScope when working with contracts which will be instantiated for you when submitting a contract function, and therefore handles all the encoding.
Full Example
Here is the full example of the encoding and decoding methods:
<<< @./snippets/encode-and-decode.ts#full{ts:line-numbers}
Working with Bytes
This guide aims to give a high-level overview of how to work with bytes in the SDK and the structures we expect them to take. For a complete overview of ABI Encoding generally, within the Fuel network, we recommend you see the ABI Encoding documentation.
Core Types
We know the sizes of all core types at compile time. They are the building blocks of the more complex types and are the most common types you will encounter.
Unsigned Integer (u8 / u16 / u32 / u64 / u128 / u256)
Each type will only contain the number of bits specified in the name. For example, a u8 will contain 8 bits, and a u256 will contain 256 bits and take up the exact property space with no additional padding.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-1{ts:line-numbers}
Boolean
A boolean is encoded as a single byte like a u8, its value being either 0 or 1.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-2{ts:line-numbers}
Fixed Length String
A fixed-length string's size is known at compile time due to the argument declaration of str[n] with n denoting its length. Each character in the string is encoded as a utf-8 bit.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-3{ts:line-numbers}
B256 / B512
These are fixed-length byte arrays, with B256 containing 256 bits and B512 containing 512 bits. You can use them for address and signature formats.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-4{ts:line-numbers}
Automatically Encoded Types
These are the types that will contain nested types and no additional encoding is required other than the encoding of the nested types. This is relevant to arrays, tuples, and structs and enums. The only caveat here, is an enum will also contain a u64 representing the enum case value. options are encoded in the same way as enums.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-5{ts:line-numbers}
Heap types
Heap types are types with a dynamic length that we do not know at compile time. These are Vec, String, and raw_slice. These types are encoded with a u64 representing the length of the data, followed by the data itself.
<<< @./snippets/working-with-bytes.ts#working-with-bytes-6{ts:line-numbers}
Full Example
Here is the full example of the working with bytes functions:
<<< @./snippets/working-with-bytes.ts#full{ts:line-numbers}
Utilities
Utilities are a set of helpers that can be used for various purposes.
Date conversion
To allow for easier manipulation of date and time, the SDK exports the DateTime class which is a wrapper around the built-in Date class. Below we will go over the methods of instantiation, utility functions and time formats.
Internally the transactions and other time/date assets are encoded using the TAI64 format. We return a DateTime class, to allow of easier conversion and formatting between the two formats.
Instantiating a DateTime
We have a host of static method for instantiation of our DateTime class.
<<< @./snippets/date-conversion/instantiation.ts#create-from-multiple-sources{ts:line-numbers}
TAI64
fromTai64 is a static method, that allows the creation of DateTime class from a TAI64 string.
toTai64 is an instance method, that allows the conversion of a DateTime class to a TAI64 string.
<<< @./snippets/date-conversion/tai64.ts#from-tai-64-and-to-tai-64{ts:line-numbers}
UNIX
fromUnixMilliseconds is a static method, that allows the creation of DateTime class from a UNIX Milliseconds number.
toUnixMilliseconds is an instance method, that allows the conversion of a DateTime class to a UNIX number in milliseconds.
<<< @./snippets/date-conversion/unix-milliseconds.ts#from-unix-milliseconds-and-to-unix-milliseconds{ts:line-numbers}
fromUnixSeconds is a static method, that allows the creation of DateTime class from a UNIX Seconds number.
toUnixSeconds is an instance method, that allows the conversion of a DateTime class to a UNIX number in seconds.
<<< @./snippets/date-conversion/unix-seconds.ts#from-unix-seconds-and-to-unix-seconds{ts:line-numbers}
Date
The DateTime class extends the functionality of the Date object, so all method are available for your usages.
<<< @./snippets/date-conversion/date-object.ts#date-object-methods{ts:line-numbers}
Formats
Here we will go over the different date/time formats that we use in the SDK. Internally the blockchain uses the TAI64 format, but we also support the UNIX format for ease of use.
UNIX Format
UNIX time is the number of seconds that have elapsed since 00:00:00 Coordinated Universal Time (UTC), Thursday, 1 January 1970, minus leap seconds. Every day is treated as if it contains exactly 86400 seconds, so leap seconds are ignored.
TAI Format
TAI stands for Temps Atomique International and is the current international real-time standard Source.
We use TAI64 is a 64-bit integer representing the number of nanoseconds since the epoch.
-
the TAI second beginning exactly (2^62 - s) seconds before the beginning of 1970 TAI, if s is between 0 inclusive and 2^62 exclusive; or
-
the TAI second beginning exactly (2^62 + s) seconds after the beginning of 1970 TAI, if s is between -2^62 inclusive and 0 exclusive.
Address
Addresses and varying address formats are commonplace when interacting with decentralized applications. Furthermore, different networks may enforce different address formats.
The Fuel Network uses the B256 address format for its interactions, an example of which can be seen below:
<<< @/../../docs/src/guide/types/snippets/b256.ts#addresses-1{ts:line-numbers}
However, a hexlified B256 (hex) is another common address format; an example can be seen below:
<<< @/../../docs/src/guide/types/snippets/evm-address/creating-an-evm.ts#snippet-2{ts:line-numbers} apps/
At times, these can even be wrapped in a Struct. Such as an Asset ID or a EVM Address:
<<< @/../../docs/src/guide/types/snippets//evm-address/using-an-evm-address-1.ts#snippet-2{ts:line-numbers}
The TS-SDK makes converting between these addresses simple using the Address helper, which provides various utilities for conversion.
The following conversion guide will show how to utilize this class to convert between address formats, as well as Sway Standard Types.
Address Conversion
This guide demonstrates how to convert between address formats and Sway Standard Types using helper functions. Native types are wrappers for bytes, and you can perform conversions between them by leveraging these functions and classes.
Converting a Contract ID
The Contract id property is an instance of the Address class. Therefore, it can be converted using the Address class functions such as toAddress and toB256:
<<< @./snippets/address-conversion/contract.ts#conversion-2{ts:line-numbers}
Converting a Wallet Address
Similarly, the Wallet address property is also of type Address and can therefore use the same Address class functions for conversion:
<<< @./snippets/address-conversion/wallet.ts#conversion-3{ts:line-numbers}
Converting an Asset ID
Asset IDs are a wrapped B256 value. The following example shows how to create an Address from a B256 type:
<<< @./snippets/address-conversion/asset-id.ts#conversion-4{ts:line-numbers}
Unit conversion
Internally, we use Arbitrary-precision arithmetic (also known as Big Number arithmetic) to allow for the handling of large numbers and different assets.
On the Fuel network, we work with 9 decimals to represent amounts under a unit. This differs from chain to chain, so it is important to know the number of decimals used on the chain you are working with.
Note: The package
@fuels/assetsprovides a list of assets and their decimals.
Below we will go over some common use cases for unit conversion.
Using our BN class we can instantiate these numbers.
<<< @./snippets/unit-conversion.ts#instantiation-1{ts:line-numbers}
Or using our bn utility function.
<<< @./snippets/unit-conversion.ts#instantiation-2{ts:line-numbers}
Contract calls
Generally, we will need to convert u64 and u256 numbers to a BN object when passing them to a Sway program from JavaScript. More information on this can be found here.
<<< @./snippets/unit-conversion.ts#contract-calls-1{ts:line-numbers}
Note: If a contract call returns a number that is too large to be represented as a JavaScript number, you can convert it to a string using the
toStringmethod instead oftoNumber.
Parsing
Parsing string-represented numbers (from user input) has never been easier, than using the parseUnits function.
<<< @./snippets/unit-conversion.ts#parse-units-1{ts:line-numbers}
We can parse large numbers.
<<< @./snippets/unit-conversion.ts#parse-units-2{ts:line-numbers}
Or numbers formatted for human readability.
<<< @./snippets/unit-conversion.ts#parse-units-3{ts:line-numbers}
We can also parse numbers in other units of measure.
<<< @./snippets/unit-conversion.ts#parse-units-4{ts:line-numbers}
Formatting
We can format common units of measure using the format function.
In the following example, we format a BigNumber representation of one Gwei, into units for the Fuel network (with 3 decimal place precision).
<<< @./snippets/unit-conversion.ts#format-1{ts:line-numbers}
We can also format numbers in other units of measure by specifying the units variable.
<<< @./snippets/unit-conversion.ts#format-2{ts:line-numbers}
A precision variable will allow for the formatting of numbers with a specific number of decimal places.
<<< @./snippets/unit-conversion.ts#format-3{ts:line-numbers}
Format units
The formatUnits function is a lesser alternative to the format function, as it will maintain the same precision as the input value.
<<< @./snippets/unit-conversion.ts#format-units-1{ts:line-numbers}
We can also format numbers in other units of measure by specifying the units variable.
<<< @./snippets/unit-conversion.ts#format-units-2{ts:line-numbers}
See also
Full Example
For the full example of unit conversion see the snippet below:
<<< @./snippets/unit-conversion.ts#full{ts:line-numbers}
Assets
We export an array of Asset objects, that can be useful when creating your dApp. The Asset object has useful metadata about the different assets that are available on blockchain networks (Fuel and Ethereum).
Included assets such as:
- Ethereum (ETH)
- Tether (USDT)
- USD Coin (USDC)
- Wrapped ETH (WETH)
The helper functions getAssetFuel and getAssetEth can be used to get an asset's details relative to each network. These return a combination of the asset, and network information (the return types are AssetFuel and AssetEth respectively).
<<< @./snippets/using-assets.ts#using-assets-1{ts:line-numbers}
Asset API
The Asset API is a RESTful API that allows you to query the assets on the Fuel blockchain. We allow for querying the Asset API on both the Mainnet and Testnet.
| Endpoint | |
|---|---|
| Mainnet | https://mainnet-explorer.fuel.network |
| Testnet | https://explorer-indexer-testnet.fuel.network |
For more information about the API, please refer to the Wiki page.
Asset by ID
We can request information about an asset by its asset ID, using the getAssetById function. This will leverage the endpoint /assets/<assetId> to fetch the asset information.
<<< @./snippets/asset-api/asset-by-id.ts#full{ts:line-numbers}
By default, we will request the asset information for mainnet. If you want to request the asset information from other networks, you can pass the network parameter (this is the same for the getAssetsByOwner function).
<<< @./snippets/asset-api/asset-by-id.ts#testnet{ts:line-numbers}
Assets by Owner
We can request information about an asset by its owner, using the getAssetsByOwner function. This will leverage the endpoint /accounts/<owner>/assets to fetch the asset information.
<<< @./snippets/asset-api/assets-by-owner.ts#full{ts:line-numbers}
You can change the pagination parameters to fetch more assets (up to 100 assets per request).
<<< @./snippets/asset-api/assets-by-owner.ts#pagination{ts:line-numbers}
Cookbook
This section covers more advanced use cases that can be satisfied by combining various features of the SDK. As such, it assumes that you have already made yourself familiar with the previous chapters of this book.
Deposit And Withdraw
Consider the following contract:
<<< @/../../docs/sway/liquidity-pool/src/main.sw#deposit-and-withdraw-cookbook-1{rust:line-numbers}
As the name implies, this contract represents a simplified version of a liquidity pool. The deposit() method allows you to supply an arbitrary amount of BASE_TOKEN. In response, it mints twice the amount of the liquidity asset to the caller's address. Similarly, the withdraw() method transfers half the amount of the BASE_TOKEN back to the caller's address.
Now, let's deposit some tokens into the liquidity pool contract. Since this requires forwarding assets to the contract, we need to pass the appropriate values to callParams when creating a contract call.
<<< @./snippets/deposit-and-withdraw/deposit.ts#deposit-and-withdraw-cookbook-2{ts:line-numbers}
As a final demonstration, let's use all our liquidity asset balance to withdraw from the pool and confirm we retrieved the initial amount. For this, we get our liquidity asset balance and supply it to the withdraw() function via callParams.
<<< @./snippets/deposit-and-withdraw/withdraw.ts#deposit-and-withdraw-cookbook-3{ts:line-numbers}
Wallet SDK and React Hooks
This guide will show you how you can use the Fuel Wallet SDK and its React Hooks to build a simple React application that lets users connect their wallet to your application and see their balance.
Setup
The first thing we will do is setup a Next.js project.
::: code-group
npm create next-app my-fuel-app
pnpm create next-app my-fuel-app
bun create next-app my-fuel-app
:::
Next, we will install the Fuel Wallet React SDK and the Fuel TypeScript SDK.
::: code-group
npm install fuels @fuels/connectors @fuels/react @tanstack/react-query
pnpm add fuels @fuels/connectors @fuels/react @tanstack/react-query
bun add fuels @fuels/connectors @fuels/react @tanstack/react-query
:::
The Provider
In order to make use of the React hooks provided by the Fuel Wallet SDK, we need to wrap our application in a FuelProvider component. This component will provide the hooks with the necessary context to interact with the Fuel Wallet SDK. Add the following to your pages/_app.tsx file:
<<< @/../../demo-wallet-sdk-react/src/app/layout.tsx#wallet-sdk-react-provider{tsx:line-numbers}
Building the UI
Go to your pages/index.tsx file and replace the contents with the following:
<<< @/../../demo-wallet-sdk-react/src/app/page.tsx#wallet-sdk-react-ui{tsx:line-numbers}
Let's break down what's happening here.
The useConnectors hook returns a list of available wallet connectors.
Once a connector has been selected by the user, the useConnect hook will return a connect function that can be used to connect the user's wallet to your application.
The useAccount hook returns information about the user's account, if they are connected.
The useBalance hook returns the user's ETH balance on the testnet network, if they are connected.
Further Reading
Custom Transactions
There may be scenarios where you need to build out transactions that involve multiple program types and assets; this can be done by instantiating a ScriptTransactionRequest. This class allows you to a append multiple program types and assets to a single transaction.
Consider the following script that transfers multiple assets to a contract:
<<< @/../../docs/sway/script-transfer-to-contract/src/main.sw#custom-transactions-1{rust:line-numbers}
This script can be executed by creating a ScriptTransactionRequest, appending the resource and contract inputs/outputs and then sending the transaction, as follows:
<<< @/../../docs/src/guide/scripts/snippets/script-custom-transaction.ts#custom-transactions-2{ts:line-numbers}
Full Example
For a full example, see below:
<<< @/../../docs/src/guide/scripts/snippets/script-custom-transaction.ts#full{ts:line-numbers}
Custom Transactions From Contract Calls
In the previous example we demonstrated how you can instantiate a ScriptTransactionRequest to customize and build out a more complex transaction via a script. The same can be done using contracts, but this allows us to utilize functions available in the contract and access on-chain state. Allowing us to harness all of the power from an invocation scope and a transaction request.
This cookbook demonstrates how we can utilize a contract call to build out a custom transaction, allowing us to update on-chain state and transfer assets to a recipient address.
<<< @./snippets/custom-contract-calls.ts#custom-transactions-contract-calls{ts:line-numbers}
Generate Fake Resources
When working with an unfunded account, you can generate fake resources to perform a dry-run on your transactions. This is useful for testing purposes without the need for real funds.
Below is an example script that returns the value 1337. You can use fake resources to execute a dry-run of this script and obtain the returned value.
<<< @/../../docs/sway/return-script/src/main.sw#generate-fake-resources-1{rust:line-numbers}
To execute a dry-run, use the Provider.dryRun method. Ensure you set the utxo_validation flag to true, as this script uses fake UTXOs:
<<< @./snippets/fake-resources.ts#generate-fake-resources-2{ts:line-numbers}
By setting utxo_validation to true, you can successfully execute the dry-run and retrieve the returned value from the script without requiring actual funds.
Transactions with Multiple Signers
When a transaction contains a spendable input such as a coin, it must also contain the signature of the coin owner for it to be spent. If the coin owner is also submitting the transaction, then this is straightforward. However, if an external address is required to sign the transaction, then the transaction must contain multiple signatures. Within the SDK, an account signature can be added to a transaction by calling addAccountWitnesses on the transaction request.
Consider a script that requires two signatures to be spent:
<<< @/../../docs/sway/script-signing/src/main.sw#multiple-signers-1{rust:line-numbers}
In the snippet above, we use the built-in Sway function tx_witness_data() to retrieve the witness signatures and tx_id() for the transaction hash. Then, we retrieve the signing address to validate the script.
We would interact with this script in the SDK by creating a transaction request from an invocation scope. The same can be done for a contract. Consider the following script:
<<< @./snippets/signing-transactions/script.ts#multiple-signers-2{ts:line-numbers}
The same approach can be used for a predicate by instantiating it and adding it to a transaction request. Consider the following predicate:
<<< @/../../docs/sway/predicate-signing/src/main.sw#multiple-signers-3{rust:line-numbers}
We can interact with this predicate in the SDK with the following implementation:
<<< @./snippets/signing-transactions/predicate.ts#multiple-signers-4{ts:line-numbers}
GraphQL Integration
The Fuel Network provides a GraphQL API to query the blockchain. To get a better understanding of the underlying schema and other operations, you can visit the playground for an interactive deep dive.
Operations
For its own purposes, the SDK creates custom operations based off of the API's schema and auto-generates TypeScript client code via codegen tools.
The end result of this code generation are the operations available on the Provider, of which some are shown below:
<<< @/../../docs/src/guide/provider/snippets/provider-operations.ts#operations{ts:line-numbers}
Note that these operations primarily serve the needs of the SDK and the Provider's methods which can encapsulate calls to multiple operations, parse the responses, etc.
If your querying needs exceed what the Provider provides, we suggest you follow this same process and write your own custom query operations, e.g.:
query getChain {
latestBlock {
transactions {
id
}
}
}
Mutations and subscriptions
For mutations and subscriptions, we strongly suggest that you communicate with the node via the Provider and do not write your own custom GraphQL operations because, in its methods, the Provider does additional processing before and after sending them to the node which might require detailed knowledge of various Fuel domain-specific topics.
Resubmitting Failed Transactions
In certain scenarios, you might need to implement a solution to resubmit failed transactions to the Fuel Network. While this approach can be effective, there are important considerations to remember.
Submission and Processing
When submitting a transaction, you will first get a response.
<<< @./snippets/resubmitting-failed-transactions/submitting.ts#resubmitting-failed-transactions-1{ts:line-numbers}
If the sendTransaction method resolves without an error, we know that the transaction was successfully submitted and accepted by the network. However, this does not guarantee that the transaction has been processed; it only indicates that the transaction has been accepted and placed in a queue for processing.
To determine whether the transaction has been processed, you must call waitForResult, which will either resolve (with the processed transaction) or reject with an error.
<<< @./snippets/resubmitting-failed-transactions/submitting.ts#resubmitting-failed-transactions-2{ts:line-numbers}
In other words:
- If
sendTransactionis rejected with an error, the transaction was not accepted by the network and is not processed. - If
waitForResultis rejected with an error, the transaction was accepted but reverted during processing.
Resources Spent When a Transaction Is Processed
If a transaction is reverted during processing, the Fuel VM will still consume the funded resources to cover the gas used up to the point of failure. After deducting the gas cost, the remaining funds will be added to a new UTXO (Unspent Transaction Output) addressed to the owner.
Attempting to resubmit the same transaction request that failed during processing will likely result in an error, as the initially spent resources no longer exist.
<<< @./snippets/resubmitting-failed-transactions/wrong-resubmission.ts#resubmitting-failed-transactions-3{ts:line-numbers}
The attempt from the above snippet will result in the error:
FuelError: Transaction is not inserted. UTXO does not exist: {{utxoId}}
To safely retry a transaction that failed during processing, you should reassemble the request from scratch and resubmit it.
<<< @./snippets/resubmitting-failed-transactions/right-resubmission.ts#resubmitting-failed-transactions-4{ts:line-numbers}
Combining UTXOs
When performing a funding operation or calling getResourcesToSpend, you may encounter the INSUFFICIENT_FUNDS_OR_MAX_COINS error if the number of coins fetched per asset exceeds the maximum limit allowed by the chain.
You may also want to do this if you want to reduce the number of inputs in your transaction, which can be useful if you are trying to reduce the size of your transaction or you are receiving the MAX_INPUTS_EXCEEDED error.
One way to avoid these errors is to combine your UTXOs. This can be done by performing a transfer that combines multiple UTXOs into a single UTXO, where the transaction has multiple inputs for the asset, but a smaller number of outputs.
Note: You will not be able to have a single UTXO for the base asset after combining, as one output will be for the transfer, and you will have another for the fees.
<<< @./snippets/combining-utxos.ts#combining-utxos{ts:line-numbers}
Max Inputs and Outputs
It's also important to note that depending on the chain configuration, you may be limited on the number of inputs and/or outputs that you can have in a transaction. These amounts can be queried via the TxParameters GraphQL query.
<<< @./snippets/max-outputs.ts#max-outputs{ts:line-numbers}
Splitting UTXOs
There may be times when you want to split one large UTXO into multiple smaller UTXOs. This can be useful if you want to send multiple concurrent transactions without having to wait for them to be processed sequentially.
Note: Depending on how many smaller UTXOs you want to create, you may need to fund the wallet with additional funds to cover the fees. As we see in the example below, we fund the sending wallet with 500 to cover the fees for the batch transfer.
<<< @./snippets/splitting-utxos.ts#splitting-utxos{ts:line-numbers}
Optimized React Example
This example implements the strategies outlined in Optimizing Frontend Apps and demonstrates how to improve the perceived speed of transactions in a React application.
import { Provider, Wallet, ScriptTransactionRequest } from "fuels";
import { useEffect, useState } from "react";
import { TestContract } from "./typegend";
import contractIds from "./typegend/contract-ids.json";
function App() {
const [request, setRequest] = useState<ScriptTransactionRequest | null>(null);
// Initialize the provider and wallet
const NETWORK_URL = "https://mainnet.fuel.network/v1/graphql";
const provider = new Provider(NETWORK_URL);
const wallet = Wallet.fromAddress("0x...", provider);
/**
* Here we'll prepare our transaction upfront on page load, so that
* by the time the user interacts with your app (i.e. clicking a btn),
* the transaction is ready to be submitted
*/
useEffect(() => {
const onPageLoad = async () => {
// 1. Connect to the contract
const contractInstance = new TestContract(
contractIds.testContract,
wallet,
);
// 2. Invoke the contract function whilst estimating and funding the
// call, which gives us the transaction request
const preparedRequest = await contractInstance.functions
.increment_counter(1)
.fundWithRequiredCoins();
setRequest(preparedRequest);
};
onPageLoad();
}, []);
/**
* By the time user user clicks the submit button, we only need to
* submit the transaction to the network
*/
const handleSubmit = async () => {
if (!request) return;
// 1. Submit the transaction to the network
const response = await wallet.sendTransaction(request);
// 2. Wait for the transaction to settle and get the result
const result = await response.waitForResult();
console.log("result", result);
};
return (
<div>
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
export default App;
Testing
This guide will teach you how to test Sway applications using the Typescript SDK.
While we use Vitest internally, we don't enforce any specific testing library or framework, so you can pick whichever you feel comfortable with.
Not using Typescript?
See also:
- Using
forc test - Using the Rust SDK
Launching a Test Node
To simplify testing in isolation, we provide a utility called launchTestNode.
It allows you to spin up a short-lived fuel-core node, set up a custom provider, wallets, deploy contracts, and much more in one go.
For usage information for launchTestNode including it's inputs, outputs and options, please check the API reference.
Explicit Resource Management
We support explicit resource management, introduced in TypeScript 5.2, which automatically calls a cleanup function after a variable instantiated with the using keyword goes out of block scope:
<<< @./snippets/launching-a-test-node.ts#automatic-cleanup{ts:line-numbers}
Configuring Typescript
To use explicit resource management, you must:
- Set your TypeScript version to
5.2or above - Set the compilation target to
es2022or below - Configure your lib setting to either include
esnextoresnext.disposable
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022", "esnext.disposable"]
}
}
Standard API
If you don't want, or can't use explicit resource management, you can use const as usual.
In this case, remember you must call .cleanup() to dispose of the node.
<<< @./snippets/launching-a-test-node.ts#manual-cleanup{ts:line-numbers}
Test Node Options
This reference describes all the options of the launchTestNode utility:
<<< @./snippets/launching-a-test-node.ts#options{ts:line-numbers}
Check out the API reference for usage information on the Test Node Options.
walletsConfig
Used to set the node's genesis block state (coins and messages).
count: number of wallets/addresses to generate on the genesis block.assets: configure how many unique assets each wallet will own with the base asset included. Can benumberorTestAssetId[].- The
TestAssetIdutility simplifies testing when different assets are necessary.
- The
coinsPerAsset: number of coins (UTXOs) per asset id.amountPerCoin: for each coin, the amount it'll contain.messages: messages to assign to the wallets.
walletsConfig.assets
The TestAssetId utility integrates with walletsConfig and gives you an easy way to generate multiple random asset ids via the TestAssetId.random static method.
<<< @./snippets/launching-a-test-node.ts#asset-ids{ts:line-numbers}
walletsConfig.messages
The TestMessage helper class is used to create messages for testing purposes. When passed via walletsConfig.messages, the recipient field of the message is overriden to be the wallet's address.
<<< @./snippets/launching-a-test-node.ts#test-messages{ts:line-numbers}
It can also be used standalone and passed into the initial state of the chain via the TestMessage.toChainMessage instance method.
<<< @./snippets/launching-a-test-node.ts#test-messages-chain{ts:line-numbers}
contractsConfigs
Used to deploy contracts on the node the launchTestNode utility launches. It's an array of objects with the following properties:
factory: contract factory class outputted bypnpm fuels typegen.walletIndex: the index of the wallets generated bywalletsConfigthat you want to deploy the contract with.options: options for contract deployment that get passed to theContractFactory.deploymethod.
nodeOptions
Options to modify the behavior of the node.
For example, you can specify your own base asset id of the chain like below:
<<< @./snippets/launching-a-test-node.ts#custom-node-options{ts:line-numbers}
Note: The API for these options is still not fully complete and better documentation will come in the future.
providerOptions
Provider options passed on Provider instantiation. More on them here.
Fuel-Core Options
The launchTestNode creates a temporary snapshot directory and configurations every time it runs. The path to this directory is passed to fuel-core via the --snapshot flag.
Default Snapshot
The default snapshot used is that of the current testnet network iteration.
Click here to see what it looks like.
Custom Snapshot
If you need a different snapshot, you can specify a DEFAULT_CHAIN_SNAPSHOT_DIR environment variable which points to your snapshot directory. launchTestNode will read that config and work with it instead, integrating all the functionality with it the same way it'd do with the default config.
How and where you specify the environment variable depends on your testing tool.
<<< @./snippets/launching-a-test-node.ts#custom-chain-config{ts:line-numbers}
Fuel-Core Node Options
Besides the snapshot, you can provide arguments to the fuel-core node via the nodeOptions.args property. For a detailed list of all possible arguments run:
fuel-core run --help
If you want all your tests to run with the same arguments, consider specifying the DEFAULT_FUEL_CORE_ARGS environment variable.
<<< @./snippets/launching-a-test-node.ts#custom-fuel-core-args{ts:line-numbers}
Basic Example
Let's use launchTestNode with the counter contract from the Fuel dApp tutorial.
Note: you will have to change the import paths of the contract factory and bytecode to match your folder structure.
<<< @./snippets/launching-a-test-node.ts#basic-example{ts:line-numbers}
Summary
- The
launchedvariable was instantiated with theusingkeyword. launchTestNodespun up a short-livedfuel-corenode, deployed a contract to it and returned it for testing.- The deployed contract is fully typesafe because of
launchTestNode's type-level integration withtypegenoutputs. - Besides the contract, you've got the provider and wallets at your disposal.
Advanced Example
A more complex example showcasing genesis block state configuration with walletsConfig and deployment of multiple contracts is shown below.
<<< @./snippets/launching-a-test-node.ts#advanced-example{ts:line-numbers}
Summary
- All points listed in the basic example apply here as well.
- Multiple wallets were generated with highly-specific coins and messages.
- It's possible to specify the wallet to be used for contract deployment via
walletIndex. - The test contract can be deployed with all the options available for real contract deployment.
Custom Blocks
You can force-produce blocks using the produceBlocks helper to achieve an arbitrary block height. This is especially useful when you want to do some testing regarding transaction maturity.
<<< @./snippets/tweaking-the-blockchain.ts#produce-blocks{ts:line-numbers}
Blocks With Custom Timestamps
You can also produce blocks with a custom block time using the produceBlocks helper by specifying the second optional parameter.
<<< @./snippets/tweaking-the-blockchain.ts#produceBlocks-custom-timestamp{ts:line-numbers}
Full Example
For a full example, see the following file: <<< @./snippets/tweaking-the-blockchain.ts#full{ts:line-numbers}
Setting up test wallets
You'll often want to create one or more test wallets when testing your contracts. Here's how to do it.
Create a single wallet
<<< @/../../docs/src/guide/wallets/snippets/access.ts#wallets{ts:line-numbers}
Setting up multiple test wallets
You can set up multiple test wallets using the launchTestNode utility via the walletsConfigs option.
To understand the different configurations, check out the walletsConfig in the test node options guide.
<<< @./snippets/launch-test-node-wallets.ts#multiple-wallets{ts:line-numbers}
Types
As you dive deeper into the SDK, it's essential to understand the variety of internal types available in both FuelVM and Sway, as well as their corresponding SDK equivalents. This section aims to provide you with the necessary knowledge to efficiently work with these types.
Overview
In this section, you will learn about:
-
FuelVM and Sway Internal Types: Discover the various types used within FuelVM and Sway, and their significance in different contexts. -
SDK Equivalents: Explore the corresponding types available in the SDK, and understand their similarities and differences compared to FuelVM and Sway internal types. -
Type Usage: Gain insights into how to effectively use these types in your projects or applications, with examples and best practices. -
Type Conversion: Learn the techniques and methods for converting between FuelVM, Sway, and SDK types, guaranteeing smooth interoperability and consistent data integrity.
Additional Resources
As you progress through the documentation, you may find it helpful to refer back to the following resources:
-
Sway Documentation: Explore the Sway documentation homepage for an overview of Sway Types, as well as other sections.
-
The Fuel Book: A comprehensive guide to the whole Fuel ecosystem.
Address
In Sway, the Address type serves as a type-safe wrapper around the primitive B256 type. The SDK takes a different approach and has its own abstraction for the Address type.
Address Class
The Address class also provides a set of utility functions for easy manipulation and conversion between address formats along with one property; b256Address, which is of the B256 type.
<<< @/../../../packages/address/src/address.ts#address-2{ts:line-numbers}
Creating an Address
There are several ways to create an Address instance:
From a b256 address
To create an Address from a 256-bit address, use the following code snippet:
<<< @./snippets/address/from-a-b256.ts#full{ts:line-numbers}
From a Public Key
To create an Address from a public key, use the following code snippet:
<<< @./snippets/address/from-a-public-key.ts#full{ts:line-numbers}
From an EVM Address
To create an Address from an EVM address, use the following code snippet:
<<< @./snippets/address/from-an-evm-address.ts#full{ts:line-numbers}
From an existing Address
To create an Address from an existing Address instance, use the following code snippet:
<<< @./snippets/address/from-an-existing-address.ts#full{ts:line-numbers}
Utility functions
equals
As you may already notice, the equals function can compare addresses instances:
<<< @./snippets/address/utilities-function-equals.ts#full{ts:line-numbers}
toChecksum
To convert an address to a checksum address, use the toChecksum function:
<<< @./snippets/address/utilities-function-to-checksum.ts#full{ts:line-numbers}
Arrays
In Sway, an Array is a fixed-size collection of elements of the same type, similar to a Tuple. Arrays can hold arbitrary types, including non-primitive types, with their size determined at compile time.
Using Arrays in the SDK
You can pass a TypeScript Array into your contract method seamlessly just like you would pass an Array to a TypeScript function.
The SDK handles the conversion from TypeScript to Sway in the background, allowing the expected data to be passed through the type regardless of the Array type.
An Array in Sway is simply a typed Array, as demonstrated in the following example:
<<< @./snippets/arrays.ts#arrays-1{ts:line-numbers}
In Sway, Arrays are fixed in size, so the storage size is determined at the time of program compilation, not during runtime.
Let's say you have a contract that takes an Array of type u64 with a size length of 2 as a parameter and returns it:
<<< @/../../docs/sway/echo-values/src/main.sw#arrays-2{rust:line-numbers}
To execute the contract call using the SDK, you would do something like this:
<<< @./snippets/arrays.ts#arrays-3{ts:line-numbers}
You can easily access and validate the Array returned by the contract method, as demonstrated in the previous example.
As previously mentioned, Sway Arrays have a predefined type and size, so you need to be careful when passing Arrays as parameters to contract calls.
Passing an Array with an incorrect size, whether it has more or fewer elements than the specified length, will result in an error:
<<< @./snippets/arrays.ts#arrays-4{ts:line-numbers}
Similarly, passing an Array with an incorrect type will also result in an error:
<<< @./snippets/arrays.ts#arrays-5{ts:line-numbers}
Vectors
If your Array size is unknown until runtime, consider using the Vectors type, which is more suitable for dynamic-sized collections.
Asset ID
An Asset ID can be represented using the AssetId type. It's definition matches the Sway standard library type being a Struct wrapper around an inner B256 value.
<<< @./snippets/asset-id/intro.ts#full{ts:line-numbers}
Using an Asset ID
You can easily use the AssetId type within your Sway programs. Consider the following contract that can compares and return an AssetId:
<<< @/../../docs/sway/echo-asset-id/src/main.sw#asset-id-2{ts:line-numbers}
The AssetId struct can be passed to the contract function as follows:
<<< @./snippets/asset-id/using-an-asset-id-1.ts#snippet-1{ts:line-numbers}
And to validate the returned value:
<<< @./snippets/asset-id/using-an-asset-id-2.ts#snippet-1{ts:line-numbers}
B256
The type B256 in Fuel represents hashes and holds a 256-bit (32-bytes) value. The TypeScript SDK represents B256 as a hexlified string value for portability and provides utilities to convert to Uint8Array when the raw bytes are required.
Generating random B256 values
To generate a random B256 value, you can use the getRandomB256() function:
<<< @./snippets/b256/generating-random-b256.ts#full{ts:line-numbers}
Converting between B256 and Uint8Array
To convert between a B256 hexlified string and a Uint8Array, you can use the arrayify and hexlify functions:
<<< @./snippets/b256/converting-between-b256-uint8.ts#full{ts:line-numbers}
Support from Address Class
A B256 value is also supported as part of the Address class, providing seamless integration with other components of your application. To create an Address instance from a b256 value, use the new Address() method:
<<< @./snippets/b256/support-from-address-class.ts#full{ts:line-numbers}
B512
In Sway, the B512 type is commonly used to handle public keys and signatures. This guide will explain how the B512 type is defined in Sway, how to visualize a B512 value using the SDK, and how to interact with a contract function that accepts a B512 parameter.
The B512 type in Sway is a wrapper around two B256 types, allowing for the representation of 64-byte values. It is defined as a struct:
<<< @/../../docs/sway/bytecode-input/src/main.sw#b512-1{rs:line-numbers}
B512 in the SDK
In the SDK, you can visualize a B512 value by examining a wallet's public key:
<<< @./snippets/b512/b512-in-the-sdk.ts#snippet-1{ts:line-numbers}
Example: Echoing a B512 Value in a Contract Function
Let's consider a contract function that accepts a B512 parameter and returns the same value:
<<< @/../../docs/sway/echo-values/src/main.sw#b512-3{rust:line-numbers}
To call this function and validate the returned value, follow these steps:
<<< @./snippets/b512/echoing-a-b512.ts#snippet-1{ts:line-numbers}
In this example, we generate a wallet, use its public key as the B512 input, call the echo_b512 contract function, and expect the returned value to match the original input.
Bytes
A dynamic array of byte values can be represented using the Bytes type, which represents raw bytes.
Using Bytes
The Bytes type can be integrated with your contract calls. Consider the following contract that can compare and return a Bytes:
<<< @/../../docs/sway/echo-bytes/src/main.sw#bytes-1{ts:line-numbers}
A Bytes array can be created using a native JavaScript array of numbers or Big Numbers, and sent to a Sway contract:
<<< @./snippets/bytes.ts#snippet-1{ts:line-numbers}
Bytes32
In Sway and the FuelVM, bytes32 is used to represent hashes. It holds a 256-bit (32-bytes) value.
Generating Random bytes32 Values
To generate a random bytes32 value, you can use the randomBytes function from the fuels module:
<<< @./snippets/bytes32/generating-random-bytes32.ts#snippet-1{ts:line-numbers}
Converting Between Byte Arrays and Strings
You can use the hexlify function to convert a byte array to a hex string, and the arrayify function to convert a hex string back to a byte array:
<<< @./snippets/bytes32/converting-between-byte.ts#snippet-1{ts:line-numbers}
Working with b256 in Fuel
In Fuel, there is a special type called b256, which is similar to bytes32. Like bytes32, B256 is also used to represent hashes and holds a 256-bit value. You can learn more about working with B256 values in the B256 documentation.
Enums
Sway Enums are a little distinct from TypeScript Enums. In this document, we will explore how you can represent Sway Enums in the SDK and how to use them with Sway contract functions.
Basic Sway Enum Example
Consider the following basic Sway Enum called StateError:
<<< @/../../docs/sway/echo-enum/src/main.sw#enums-1{rust:line-numbers}
The type () indicates that there is no additional data associated with each Enum variant. Sway allows you to create Enums of Enums or associate types with Enum variants.
Using Sway Enums As Function Parameters
Let's define a Sway contract function that takes a StateError Enum variant as an argument and returns it:
<<< @/../../docs/sway/echo-enum/src/main.sw#enums-2{rust:line-numbers}
To execute the contract function and validate the response, we can use the following code:
<<< @./snippets/enums/using-sway-enums.ts#snippet-1{ts:line-numbers}
In this example, we simply pass the Enum variant as a value to execute the contract function call.
Enum of Enums Example
In this example, the Error Enum is an Enum of two other Enums: StateError and UserError.
<<< @/../../docs/sway/echo-enum/src/main.sw#enums-4{rust:line-numbers}
Using Enums of Enums with Contract Functions
Now, let's create a Sway contract function that accepts any variant of the Error Enum as a parameter and returns it immediately. This variant could be from either the StateError or UserError Enums.
<<< @/../../docs/sway/echo-enum/src/main.sw#enums-5{rust:line-numbers}
Since the Error Enum is an Enum of Enums, we need to pass the function parameter differently. The parameter will be a TypeScript object:
<<< @./snippets/enums/using-enums-of-enums-1.ts#snippet-1{ts:line-numbers}
In this case, since the variant InsufficientPermissions belongs to the UserError Enum, we create a TypeScript object using the Enum name as the object key and the variant as the object value.
We would follow the same approach if we intended to use a variant from the StateError Enum:
<<< @./snippets/enums/using-enums-of-enums-2.ts#snippet-1{ts:line-numbers}
Errors
While working with enums, you may run into the following issues:
Using an invalid enum type
Thrown when the type being passed to the enum does not match that expected by it.
<<< @./snippets/enums/using-an-invalid-enum-type.ts#snippet-1{ts:line-numbers}
Using an invalid enum value
Thrown when the parameter passed is not an expected enum value.
<<< @./snippets/enums/using-an-invalid-enum-value.ts#snippet-1{ts:line-numbers}
Using an invalid enum case key
Thrown when the passed enum case is not an expected enum case value.
<<< @./snippets/enums/using-an-invalid-enum-case.ts#snippet-1{ts:line-numbers}
EvmAddress
An Ethereum Virtual Machine (EVM) Address can be represented using the EvmAddress type. It's definition matches the Sway standard library type being a Struct wrapper around an inner B256 value.
<<< @./snippets/evm-address/intro.ts#snippet-1{ts:line-numbers}
Creating an EVM Address
An EVM Address only has 20 bytes therefore the first 12 bytes of the B256 value are set to 0. Within the SDK, an Address can be instantiated and converted to a wrapped and Sway compatible EVM Address using the toEvmAddress() function:
<<< @./snippets/evm-address/creating-an-evm.ts#snippet-1{ts:line-numbers}
Using an EVM Address
The EvmAddress type can be integrated with your contract calls. Consider the following contract that can compare and return an EVM Address:
<<< @/../../docs/sway/echo-evm-address/src/main.sw#evm-address-1{ts:line-numbers}
The EvmAddress type can be used with the SDK and passed to the contract function as follows:
<<< @./snippets/evm-address/using-an-evm-address-1.ts#snippet-1{ts:line-numbers}
And to validate the returned value:
<<< @./snippets/evm-address/using-an-evm-address-2.ts#snippet-1{ts:line-numbers}
Native Parameter Types
Below you can find examples of how to convert between common native Sway program input and output types:
Address
AddressInput
To pass an Address as an input parameter to a Sway program, you can define the input as shown below:
<<< @./snippets/native-parameters/address.ts#address-input
AddressOutput
For a Sway program that returns an Address type, you can convert the returned value to an Address type in fuels as shown below:
<<< @./snippets/native-parameters/address.ts#address-output
ContractId
ContractIdInput
To pass a ContractId as an input parameter to a Sway program, you can define the input as shown below:
<<< @./snippets/native-parameters/contract-id.ts#contract-id-input
ContractIdOutput
For a Sway program that returns a ContractId type, you can convert the returned value to a string as shown below:
<<< @./snippets/native-parameters/contract-id.ts#contract-id-output
Identity
IdentityInput
To pass an Identity as an input parameter to a Sway program, you can define the input as shown below:
For an address:
<<< @./snippets/native-parameters/identity-address.ts#identity-address-input
For a contract:
<<< @./snippets/native-parameters/identity-contract.ts#identity-contract-input
IdentityOutput
For a Sway program that returns an Identity type, you can convert the returned value to an Address or string as shown below:
For an address:
<<< @./snippets/native-parameters/identity-address.ts#identity-address-output
For a contract:
<<< @./snippets/native-parameters/identity-contract.ts#identity-contract-output
AssetId
AssetIdInput
To pass an AssetId as an input parameter to a Sway program, you can define the input as shown below:
<<< @./snippets/native-parameters/asset-id.ts#asset-id-input
AssetIdOutput
For a Sway program that returns an AssetId type, you can convert the returned value to a string as shown below:
<<< @./snippets/native-parameters/asset-id.ts#asset-id-output
Numbers
In Sway, there are multiple primitive number types:
u8(8-bit unsigned integer)u16(16-bit unsigned integer)u32(32-bit unsigned integer)u64(64-bit unsigned integer)u256(256-bit unsigned integer)
This guide explains how to create and interact with Sway numbers while using the SDK.
Creating Numbers
For u64 and u256
When you pass in a u64 or a u256 to a Sway program from JavaScript, you must first convert it to a BigNum object. This is because these types can have extremely large maximum values (2^64 and 2^256 respectively), and JavaScript's Number type can only hold up to 53 bits of precision (2^53).
<<< @./snippets/numbers/for-u64-and-u256-1.ts#snippet-1{ts:line-numbers}
You can also create a BigNum from a string. This is useful when you want to pass in a number that is too large to be represented as a JavaScript number. Here's how you can do that:
<<< @./snippets/numbers/for-u64-and-u256-2.ts#snippet-1{ts:line-numbers}
For u8, u16, and u32
You don't need to do anything special to create these numbers. You can pass in a JavaScript number directly. See the examples below for more details.
Examples: Interacting with Numbers in Contract Methods
For u64 and u256
<<< @./snippets/numbers/for-u64-and-u256-3.ts#snippet-1{ts:line-numbers}
Note: If a contract call returns a number that is too large to be represented as a JavaScript number, you can convert it to a string using the
.toString()method instead of.toNumber().
For u8, u16, and u32
<<< @./snippets/numbers/for-u8-u16-and-u32-1.ts#snippet-1{ts:line-numbers}
Using a BigNum from ethers with fuels
<<< @./snippets/numbers/for-u8-u16-and-u32-2.ts#snippet-1{ts:line-numbers}
Options
Sway provides the Option (optional) container for handling variables that can have a value or be marked as no-value. This concept is useful when dealing with situations where a variable may or may not have a defined value.
In this guide, we'll explain how to work with Option types in Sway and demonstrate their usage through a practical example.
Overview of Option Type
The Option type in Sway is a special wrapper type of Enum. In TypeScript, you can represent the Option type by using the undefined keyword, as shown in the following example
<<< @./snippets/options/overview-of-option.ts#snippet-1{ts:line-numbers}
In this example, the variable input1 can be either a number or undefined.
Example: Option<u8> Parameters
Let's say we have a contract function that accepts two Option<u8> parameters. Both of these parameters can have a value or be undefined. The function checks whether each input has a value; if not, it assigns a value of 0. Finally, the function returns the sum of the two inputs.
Here's the contract function written in Sway:
<<< @/../../docs/sway/sum-option-u8/src/main.sw#options-2{rust:line-numbers}
You can interact with the contract function using the SDK as follows:
<<< @./snippets/options/overview-of-option.ts#snippet-2{ts:line-numbers}
In this case, the result of the contract function call is the sum of both input parameters. If we pass only one parameter, the contract function will default the other parameter's value to 0.
<<< @./snippets/options/example-option-u8.ts#snippet-1{ts:line-numbers}
Using Option types in Sway allows you to elegantly handle situations where a variable may or may not have a defined value.
RawSlice
A dynamic array of values can be represented using the RawSlice type. A raw slice can be a value reference or a raw pointer.
Using a RawSlice
The RawSlice type can be integrated with your contract calls. Consider the following contract that can compare and return a RawSlice:
<<< @/../../docs/sway/echo-raw-slice/src/main.sw#raw-slice-1{ts:line-numbers}
A RawSlice can be created using a native JavaScript array of numbers or Big Numbers, and sent to a Sway contract:
<<< @./snippets/raw-slice.ts#raw-slice-2{ts:line-numbers}
StdString
A dynamic string of variable length can be represented using the StdString type, also known as a Standard Lib String or std-lib-string. It behaves much like a dynamic string in most languages, and is essentially an array of characters.
Using a StdString
The StdString type can be integrated with your contract calls. Consider the following contract that can compare and return a String:
<<< @/../../docs/sway/echo-std-string/src/main.sw#std-string-1{ts:line-numbers}
A string can be created using a native JavaScript string, and sent to a Sway contract:
<<< @./snippets/std-string.ts#std-string-2{ts:line-numbers}
String
In Sway, strings are statically-sized, which means you must define the size of the string beforehand. Statically-sized strings are represented using the str[x] syntax, where x indicates the string's size.
This guide explains how to create and interact with statically-sized strings while using the SDK.
Creating Statically-Sized Strings
<<< @./snippets/string.ts#string-1{ts:line-numbers}
Interacting with Statically-Sized Strings in Contract Methods
When a contract method accepts and returns a str[8], the corresponding SDK wrapper method will also take and return a string of the same length. You can pass a string to the contract method like this:
<<< @./snippets/string.ts#string-2{ts:line-numbers}
When working with statically-sized strings, ensure that the input and output strings have the correct length to avoid erroneous behavior.
If you pass a string that is either too long or too short for a contract method, the call will fail like this:
<<< @./snippets/string.ts#string-3{ts:line-numbers}
Structs
In Sway, a struct serves a similar purpose as an Object in TypeScript. It defines a custom data structure with specified property names and types. The property names and types in the Sway struct must match the corresponding TypeScript definition.
Example
Here is an example of a struct in Sway:
<<< @/../../docs/sway/employee-data/src/lib.sw#struct-1{rust:line-numbers}
And here is the equivalent structure represented in TypeScript:
<<< @./snippets/structs.ts#struct-2{ts:line-numbers}
Handling Different Data Types
Please note that TypeScript does not have native support for u8 and u64 types. Instead, use the number type to represent them.
Additionally, TypeScript does not support specifying string length, so just use string for the name.
In a similar way, since the type B256 on the SDK is just an hexlified string, we use string as well.
Tuples
In Sway, Tuples are fixed-length collections of heterogeneous elements. Tuples can store multiple data types, including basic types, structs, and enums. This guide will demonstrate how to represent and work with Tuples in TypeScript and interact with a contract function that accepts a tuple as a parameter.
In TypeScript, you can represent Sway tuples using arrays with specified types for each element:
<<< @./snippets/tuples.ts#tuples-1{ts:line-numbers}
In this example, the Typescript tuple variable contains three elements of different types: a number, a boolean, and another number.
Example: Passing Tuple as a Parameter
Let's consider a contract function that accepts a tuple as a parameter and returns the same Tuple:
<<< @/../../docs/sway/echo-values/src/main.sw#tuples-2{rust:line-numbers}
To execute and validate the contract function using the SDK, follow these steps:
<<< @./snippets/tuples.ts#tuples-3{ts:line-numbers}
In this example, we create a Tuple with three elements, call the echo_tuple contract function, and expect the returned tuple to match the original one. Note that we convert the third element of the returned tuple to a number using new BN(value[2]).toNumber().
Tuples in Sway provide a convenient way to store and manipulate collections of heterogeneous elements. Understanding how to represent and work with tuples in TypeScript and Sway contracts will enable you to create more versatile and expressive code.
Vectors
In Sway, a Vector is a dynamic-sized collection of elements of the same type. Vectors can hold arbitrary types, including non-primitive types.
Working with Vectors in the SDK
A basic Vector in Sway is similar to a TypeScript Array:
<<< @./snippets/vectors.ts#vector-1{ts:line-numbers}
Consider the following example of a EmployeeData struct in Sway:
<<< @/../../docs/sway/employee-data/src/lib.sw#struct-1{rust:line-numbers}
Now, let's look at the following contract method. It receives a Vector of the Transaction struct type as a parameter and returns the last Transaction entry from the Vector:
<<< @/../../docs/sway/echo-employee-data-vector/src/main.sw#vector-3{ts:line-numbers}
The code snippet below demonstrates how to call this Sway contract method, which accepts a Vec<Transaction>:
<<< @./snippets/vectors.ts#vector-4{ts:line-numbers}
Converting Bytecode to Vectors
Some functions require you to pass in bytecode to the function. The type of the bytecode parameter is usually Vec<u8>, here's an example of how to pass bytecode to a function:
<<< @/../../docs/sway/bytecode-input/src/main.sw#vector-bytecode-input-sway{ts:line-numbers}
To pass bytecode to this function, you can make use of the arrayify function to convert the bytecode file contents into a UInt8Array, the TS compatible type for Sway's Vec<u8> type and pass it the function like so:
<<< @./snippets/vectors.ts#vector-bytecode-input-ts{ts:line-numbers}
Errors
All errors thrown from the SDK are instances of the FuelError class which will have an accompanying ErrorCode.
Error Codes
Here is a list of the expected error codes the SDK can throw. These error codes are used to help understand the error that has been thrown with potential resolutions.
ABI_MAIN_METHOD_MISSING
When your ABI does not have a main method.
This can be resolved by adding a main method to your ABI. This is prevalent in scripts and predicates that must contain a main method.
ABI_TYPES_AND_VALUES_MISMATCH
When the arguments supplied to the function do not match the minimum required input length.
Check that the arguments supplied to the function match the required type.
ACCOUNT_REQUIRED
When an Account is required for an operation. This will usually be in the form of a Wallet.
It could be caused during the deployments of contracts when an account is required to sign the transaction. This can be resolved by following the deployment guide here.
ASSET_BURN_DETECTED
When you are trying to send a transaction that will result in an asset burn.
Add relevant coin change outputs to the transaction, or enable asset burn in the transaction request.
CONFIG_FILE_NOT_FOUND
When a configuration file is not found. This could either be a fuels.config.[ts,js,mjs,cjs] file or a TOML file.
Ensure that the configuration file is present in the root directory of your project.
CONFIG_FILE_ALREADY_EXISTS
When a configuration file already exists in the root directory of your project.
You can not run fuels init more than once for a given project. Either remove the existing configuration file or update it.
CONVERTING_FAILED
When converting a big number into an incompatible format.
Ensure that the value you've supplied to the big number is compatible with the value you are converting to.
CONTRACT_SIZE_EXCEEDS_LIMIT
When the contract size exceeds the maximum contract size limit.
Ensure that the contract size is less than the maximum contract size limit, of 100 KB. This can be validated by checking the bytecode length of the contract.
DUPLICATED_POLICY
When there are more than policies with the same type, for a transaction.
Ensure that there are no duplicate (by type) policies for a transaction.
ERROR_BUILDING_BLOCK_EXPLORER_URL
When more than one of the following options is passed: path, address, txId, blockNumber.
Check that only one of the above is passed.
FUNCTION_NOT_FOUND
When the function with the given name, signature or selector is not found in the ABI.
Check that the function name, signature or selector is correct and exits on the ABI.
FUNDS_TOO_LOW
When the funds in the account are lower than the required amount.
Ensure that the account has enough funds to cover the transaction.
GAS_LIMIT_TOO_LOW
When the gas limit is lower than the minimum gas limit.
Increase the gas limit to be greater than the minimum gas limit.
GAS_PRICE_TOO_LOW
When the gas price is lower than the minimum gas price.
Increase the gas price to be greater than the minimum gas price.
HD_WALLET_ERROR
A hardware wallet will throw for unsupported configurations.
The error message will determine which element of the configuration is incorrect. It could be due to the public or private key or when configuring to/from an extended key.
INVALID_CHECKSUM
Checksum validation failed for the provided mnemonic.
Ensure that the mnemonic is correct.
INVALID_CHUNK_SIZE_MULTIPLIER
When the chunk size multiplier is not between 0 and 1.
Ensure that the chunk size multiplier is a number that it is between 0 and 1.
INVALID_CONFIGURABLE_CONSTANTS
When the program type either: does not have configurable constants to be set; or the provided configurable constant does not belong to the program type, as defined by its ABI.
Ensure the configurable constants provided are correct and are defined in ABI.
INVALID_COMPONENT
When an expected component is not found in the ABI or is malformed.
Ensure that you have correctly formed Sway types for Arrays and Vectors.
INVALID_CREDENTIALS
When the password provided is incorrect.
Ensure that the password is correct.
INVALID_DATA
When the value being passed is not considered valid, as defined by the function.
Check the function signature and ensure that the passed value is valid.
INVALID_ENTROPY
When the entropy is not: between 16 and 32 bytes; a multiple of 4.
Ensure that the entropy is between 16 and 32 bytes and a multiple of 4.
INVALID_EVM_ADDRESS
When the provided EVM address is invalid.
Ensure that the EVM address is valid.
INVALID_INPUT_PARAMETERS
When the provided input parameters are not valid.
The error message will determine which parameter is missing. It could be that the provided program type is not one of the following contract, script, or predicate.
INVALID_MNEMONIC
When the supplied mnemonic is invalid.
Check the message for more details. It could be that the mnemonic phrase word length is not one of the following: 12, 15, 18, 21, or 24 lengths.
INVALID_PASSWORD
When the provided password is incorrect.
Ensure that the password is correct.
INVALID_POLICY_TYPE
When the supplied policy type is invalid for the given Script.
Check the policy type is defined in PolicyType.
INVALID_PROVIDER
When unable to connect to the Provider or Network supplied to a method on the Fuel class.
Check that the Provider or Network is supplied correctly.
INVALID_PUBLIC_KEY
When the provided public key is invalid.
Ensure that the public key is valid.
INVALID_RECEIPT_TYPE
When the receipt type is invalid.
Check the type is within ReceiptType.
INVALID_REQUEST
When the request to the Fuel node fails, error messages are propagated from the Fuel node.
Check the error message from the Fuel node.
INVALID_SEED
When the seed length is not between 16 and 64 bytes.
Ensure that the seed length is between 16 and 64 bytes.
INVALID_TRANSACTION_INPUT
When the input type is invalid.
Check the type is within InputType.
INVALID_TRANSACTION_OUTPUT
When the output type is invalid.
Check the type is within OutputType.
INVALID_TRANSACTION_STATUS
When the transaction status received from the node is unexpected.
Check the status received is within TransactionStatus.
UNSUPPORTED_TRANSACTION_TYPE
When the transaction type from the Fuel Node is not supported.
The type is within TransactionType.
INVALID_TTL
When the TTL is less than or equal to zero.
Ensure that the TTL is a number and that the TTL is greater than zero.
INVALID_WORD_LIST
When the word list length is not equal to 2048.
The word list provided to the mnemonic length should be equal to 2048.
INVALID_URL
When the URL provided is invalid.
Ensure that the URL is valid.
JSON_ABI_ERROR
When an ABI type does not conform to the correct format.
It is usually caused by an incorrect type/s within your program, check our type docs here for information on the types we support and their expected format.
LOG_TYPE_NOT_FOUND
When the log type ID supplied can not be found in the ABI.
Check that the log type ID is correct and exists in the ABI.
MISSING_CONNECTOR
A connector is missing when it's required for a given operation.
Ensure that a connector has been supplied to the Account or Wallet.
MISSING_PROVIDER
A provider is missing when it's required for a given operation.
It could be caused by the provider not being set for either an Account or a Wallet - use the connect method to attach a provider.
MISSING_REQUIRED_PARAMETER
When a required parameter has not been supplied to a given method.
The error message will determine which parameter is missing. This could be caused during type generation when neither inputs nor filepaths are supplied (at least one is required).
NODE_INFO_CACHE_EMPTY
When the Fuel Node info cache is empty; This is usually caused by not being connected to the Fuel Node.
Ensure that the provider has connected to a Fuel Node successfully.
INSUFFICIENT_FUNDS_OR_MAX_COINS
This error can occur during a funding operation or when calling the getResourcesToSpend method. It indicates one of the following issues:
Insufficient Balance: The specified account does not have enough balance to cover the required amount.
UTXO Limit Exceeded: Although the account has enough total funds, the funds are spread across too many UTXOs (coins). The blockchain limits how many UTXOs can be used in a single transaction, and exceeding this limit prevents the transaction from being processed.
First, to be sure what the real reason is, you can fetch the balance of the assetId to ensure that the account has enough funds to cover the amount. After knowing the reason, to solve you can:
For Insufficient Balance: Acquire additional funds in the required asset to meet the amount needed.
For UTXO Limit Exceeded: Combine UTXOs to reduce their number and meet the network's requirements. You can follow this guide to learn how to combine UTXOs effectively.
TIMEOUT_EXCEEDED
When the timeout has been exceeded for a given operation.
Check that you're connected to the network and that the network is stable.
TYPE_NOT_FOUND
When the type with the given type ID is not found in the ABI.
Check that the type ID is correct and exists in the ABI.
TYPE_NOT_SUPPORTED
When an unexpected type has been detected - the error message will determine which type is incorrect.
Check the type against your ABI and ensure that it is correct. You can find a list of all our types here.
UNSUPPORTED_FUEL_CLIENT_VERSION
When the version of the Fuel Node you are targeting is not supported by the client you are accessing it from.
Check the version of the Fuel Node and use a compatible version of the SDK to target it.
WALLET_MANAGER_ERROR
A wallet manager will throw for a multitude of reasons. The error message will determine which element of the configuration is incorrect.
It could be that the passphrase is incorrect and/or the wallet does not exist in the manager.
WORKSPACE_NOT_DETECTED
When the workspace is not detected in the directory indicated in the message.
Ensure that the workspace is present in the directory specified.
UNKNOWN
In cases where the error hasn't been mapped yet, this code will be used.
If you believe you found a bug, please report the issue to the team.
MAX_INPUTS_EXCEEDED
When the number of transaction inputs exceeds the maximum limit allowed by the blockchain.
MAX_OUTPUTS_EXCEEDED
When the number of transaction outputs exceeds the maximum limit allowed by the blockchain.
Transaction Format
The Fuel Transaction Format.
Consensus Parameters
| name | type | description |
|---|---|---|
GAS_PER_BYTE | uint64 | Gas charged per byte of the transaction. |
GAS_PRICE_FACTOR | uint64 | Unit factor for gas price. |
MAX_GAS_PER_TX | uint64 | Maximum gas per transaction. |
MAX_INPUTS | uint64 | Maximum number of inputs. |
MAX_OUTPUTS | uint64 | Maximum number of outputs. |
MAX_PREDICATE_LENGTH | uint64 | Maximum length of predicate, in instructions. |
MAX_GAS_PER_PREDICATE | uint64 | Maximum gas per predicate. |
MAX_PREDICATE_DATA_LENGTH | uint64 | Maximum length of predicate data, in bytes. |
MAX_SCRIPT_LENGTH | uint64 | Maximum length of script, in instructions. |
MAX_SCRIPT_DATA_LENGTH | uint64 | Maximum length of script data, in bytes. |
MAX_MESSAGE_DATA_LENGTH | uint64 | Maximum length of message data, in bytes. |
MAX_STORAGE_SLOTS | uint64 | Maximum number of initial storage slots. |
MAX_TRANSACTION_SIZE | uint64 | Maximum size of a transaction, in bytes. |
MAX_WITNESSES | uint64 | Maximum number of witnesses. |
MAX_BYTECODE_SUBSECTIONS | uint64 | Maximum number of bytecode subsections. |
CHAIN_ID | uint64 | A unique per-chain identifier. |
BASE_ASSET_ID | bytes32 | The base asset of the chain. |
PRIVILEGED_ADDRESS | bytes32 | The privileged address of the network who can perform upgrade. |
Transaction
enum TransactionType : uint8 {
Script = 0,
Create = 1,
Mint = 2,
Upgrade = 3,
Upload = 4,
Blob = 5,
}
| name | type | description |
|---|---|---|
type | TransactionType | Transaction type. |
data | One of TransactionScript, TransactionCreate, TransactionMint, TransactionUpgrade, or TransactionUpload | Transaction data. |
Given helper max_gas() returns the maximum gas that the transaction can use.
Given helper count_ones() that returns the number of ones in the binary representation of a field.
Given helper count_variants() that returns the number of variants in an enum.
Given helper sum_variants() that sums all variants of an enum.
Transaction is invalid if:
typeis not validTransactionTypevalueinputsCount > MAX_INPUTSoutputsCount > MAX_OUTPUTSwitnessesCount > MAX_WITNESSESsize > MAX_TRANSACTION_SIZE. The size of a transaction is calculated as the sum of the sizes of its static and dynamic parts, as determined by canonical serialization.- No inputs are of type
InputType.CoinorInputType.Messagewithinput.dataLength== 0 - More than one output is of type
OutputType.Changefor any asset ID in the input set - More than one output is of type
OutputType.Changewith identicalasset_idfields. - Any output is of type
OutputType.Changefor any asset ID not in the input set - More than one input of type
InputType.Coinfor any Coin ID in the input set - More than one input of type
InputType.Contractfor any Contract ID in the input set - More than one input of type
InputType.Messagefor any Message ID in the input set - if
type != TransactionType.Mintmax_gas(tx) > MAX_GAS_PER_TX- No policy of type
PolicyType.MaxFeeis set count_ones(policyTypes) > count_variants(PolicyType)policyTypes > sum_variants(PolicyType)len(policies) > count_ones(policyTypes)
When serializing a transaction, fields are serialized as follows (with inner structs serialized recursively):
uint8,uint16,uint32,uint64: big-endian right-aligned to 8 bytes.byte[32]: as-is.byte[]: as-is, with padding zeroes aligned to 8 bytes.
When deserializing a transaction, the reverse is done. If there are insufficient bytes or too many bytes, the transaction is invalid.
TransactionScript
enum ReceiptType : uint8 {
Call = 0,
Return = 1,
ReturnData = 2,
Panic = 3,
Revert = 4,
Log = 5,
LogData = 6,
Transfer = 7,
TransferOut = 8,
ScriptResult = 9,
MessageOut = 10,
Mint = 11,
Burn = 12,
}
| name | type | description |
|---|---|---|
scriptGasLimit | uint64 | Gas limits the script execution. |
receiptsRoot | byte[32] | Merkle root of receipts. |
scriptLength | uint64 | Script length, in instructions. |
scriptDataLength | uint64 | Length of script input data, in bytes. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
script | byte[] | Script to execute. |
scriptData | byte[] | Script input data (parameters). |
policies | Policy[] | List of policies, sorted by PolicyType. |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Given helper len() that returns the number of bytes of a field.
Transaction is invalid if:
- Any output is of type
OutputType.ContractCreated scriptLength > MAX_SCRIPT_LENGTHscriptDataLength > MAX_SCRIPT_DATA_LENGTHscriptLength * 4 != len(script)scriptDataLength != len(scriptData)
Note: when signing a transaction,
receiptsRootis set to zero.Note: when verifying a predicate or executing a script,
receiptsRootis initialized to zero.
The receipts root receiptsRoot is the root of the binary Merkle tree of receipts. If there are no receipts, its value is set to the root of the empty tree, i.e. 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
TransactionCreate
| name | type | description |
|---|---|---|
bytecodeWitnessIndex | uint16 | Witness index of contract bytecode to create. |
salt | byte[32] | Salt. |
storageSlotsCount | uint64 | Number of storage slots to initialize. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
storageSlots | (byte[32], byte[32]])[] | List of storage slots to initialize (key, value). |
policies | Policy[] | List of policies. |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Transaction is invalid if:
- Any input is of type
InputType.ContractorInputType.Messagewhereinput.dataLength > 0 - Any input uses non-base asset.
- Any output is of type
OutputType.ContractorOutputType.VariableorOutputType.Message - Any output is of type
OutputType.Changewith non-baseasset_id - It does not have exactly one output of type
OutputType.ContractCreated tx.data.witnesses[bytecodeWitnessIndex].dataLength > CONTRACT_MAX_SIZEbytecodeWitnessIndex >= tx.witnessesCount- The keys of
storageSlotsare not in ascending lexicographic order - The computed contract ID (see below) is not equal to the
contractIDof the oneOutputType.ContractCreatedoutput storageSlotsCount > MAX_STORAGE_SLOTS- The Sparse Merkle tree root of
storageSlotsis not equal to thestateRootof the oneOutputType.ContractCreatedoutput
Creates a contract with contract ID as computed here.
TransactionMint
The transaction is created by the block producer and is not signed. Since it is not usable outside of block creation or execution, all fields must be fully set upon creation without any zeroing.
This means that the transaction ID must also include the correct txPointer value, not zeroed out.
| name | type | description |
|---|---|---|
txPointer | TXPointer | The location of the Mint transaction in the block. |
inputContract | InputContract | The contract UTXO that assets are minted to. |
outputContract | OutputContract | The contract UTXO that assets are being minted to. |
mintAmount | uint64 | The amount of funds minted. |
mintAssetId | byte[32] | The asset IDs corresponding to the minted amount. |
gasPrice | uint64 | The gas price to be used in calculating all fees for transactions on block |
Transaction is invalid if:
txPointeris zero or doesn't match the block.outputContract.inputIndexis not zero
TransactionUpgrade
The Upgrade transaction allows upgrading either consensus parameters or state transition function used by the network to produce future blocks. The Upgrade Purpose type defines the purpose of the upgrade.
The Upgrade transaction is chargeable, and the sender should pay for it. Transaction inputs should contain only base assets.
Only the privileged address from ConsensusParameters can upgrade the network. The privileged address can be either a real account or a predicate.
When the upgrade type is UpgradePurposeType.ConsensusParameters serialized consensus parameters are available in the witnesses and the Upgrade transaction is self-contained because it has all the required information.
When the upgrade type is UpgradePurposeType.StateTransition, the bytecodeRoot field contains the Merkle root of the new bytecode of the state transition function. The bytecode should already be available on the blockchain at the upgrade point; otherwise, the upgrade will fail. The bytecode can be part of the genesis block or can be uploaded via the TransactionUpload transaction.
The block header contains information about which versions of consensus parameters and state transition function are used to produce a block, and the Upgrade transaction defines behavior corresponding to the version. When the block executes the Upgrade transaction, it defines new behavior for either BlockHeader.consensusParametersVersion + 1 or BlockHeader.stateTransitionBytecodeVersion + 1(it depends on the purpose of the upgrade).
When the Upgrade transaction is included in the block, it doesn't affect the current block execution. Since behavior is now defined, the inclusion of the Upgrade transaction allows the production of the next block with a new version. The block producer can still continue to use the previous version and start using a new version later unless the state transition function forbids it.
The behavior is set once per version. It is forbidden to override the behavior of the network. Each behavior should have its own version. The version should grow monotonically without jumps.
The BlockHeader.consensusParametersVersion and BlockHeader.stateTransitionBytecodeVersion are independent and can grow at different speeds.
| name | type | description |
|---|---|---|
upgradePurpose | UpgradePurpose | The purpose of the upgrade. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
policies | Policy[] | List of policies. |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Transaction is invalid if:
- Any input is of type
InputType.ContractorInputType.Messagewhereinput.dataLength > 0 - Any input uses non-base asset.
- Any output is of type
OutputType.ContractorOutputType.VariableorOutputType.MessageorOutputType.ContractCreated - Any output is of type
OutputType.Changewith non-baseasset_id - No input where
InputType.Message.owner == PRIVILEGED_ADDRESSorInputType.Coint.owner == PRIVILEGED_ADDRESS - The
UpgradePurposeis invalid
TransactionUpload
The Upload transaction allows the huge bytecode to be divided into subsections and uploaded slowly to the chain. The Binary Merkle root built on top of subsections is an identifier of the bytecode.
Each transaction uploads a subsection of the code and must contain proof of connection to the root. All subsections should be uploaded sequentially, which allows the concatenation of previously uploaded subsections with new subsection. The bytecode is considered final when the last subsection is uploaded, and future Upload transactions with the same root fields should be rejected.
When the bytecode is completed it can be used to upgrade the network.
The size of each subsection can be arbitrary; the only limit is the maximum number of subsections allowed by the network. The combination of the transaction gas limit and the number of subsections limits the final maximum size of the bytecode.
| name | type | description |
|---|---|---|
root | byte[32] | The root of the Merkle tree is created over the bytecode. |
witnessIndex | uint16 | The witness index of the subsection of the bytecode. |
subsectionIndex | uint16 | The index of the subsection of the bytecode. |
subsectionsNumber | uint16 | The total number of subsections on which bytecode was divided. |
proofSetCount | uint16 | Number of Merkle nodes in the proof. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
proofSet | byte[32][] | The proof set of Merkle nodes to verify the connection of the subsection to the root. |
policies | Policy[] | List of policies. |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Transaction is invalid if:
- Any input is of type
InputType.ContractorInputType.Messagewhereinput.dataLength > 0 - Any input uses non-base asset.
- Any output is of type
OutputType.ContractorOutputType.VariableorOutputType.MessageorOutputType.ContractCreated - Any output is of type
OutputType.Changewith non-baseasset_id witnessIndex >= tx.witnessesCountsubsectionIndex>=subsectionsNumbersubsectionsNumber > MAX_BYTECODE_SUBSECTIONS- The Binary Merkle tree root calculated from
(witnesses[witnessIndex], subsectionIndex, subsectionsNumber, proofSet)is not equal to theroot. Root calculation is affected by all fields, so modification of one of them invalidates the proof.
TransactionBlob
The Blob inserts a simple binary blob in the chain. It's raw immutable data that can be cheaply loaded by the VM and used as instructions or just data. Unlike Create, it doesn't hold any state or balances.
Blobs are content-addressed, i.e. the they are uniquely identified by hash of the data field. Programs running on the VM can load an already-posted blob just by the hash, without having to specify it in contract inputs.
| name | type | description |
|---|---|---|
id | byte[32] | Blob id, i.e. hash of the data. |
witnessIndex | uint16 | The witness index of the data. |
policyTypes | uint32 | Bitfield of used policy types. |
inputsCount | uint16 | Number of inputs. |
outputsCount | uint16 | Number of outputs. |
witnessesCount | uint16 | Number of witnesses. |
policies | Policy[] | List of policies. |
inputs | Input[] | List of inputs. |
outputs | Output[] | List of outputs. |
witnesses | Witness[] | List of witnesses. |
Transaction is invalid if:
- Any input is of type
InputType.ContractorInputType.Messagewhereinput.dataLength > 0 - Any input uses non-base asset.
- Any output is of type
OutputType.ContractorOutputType.VariableorOutputType.MessageorOutputType.ContractCreated - Any output is of type
OutputType.Changewith non-baseasset_id witnessIndex >= tx.witnessesCountsha256(witnesses[witnessIndex]) != id
UpgradePurposeType
enum UpgradePurposeType : uint8 {
ConsensusParameters = 0,
StateTransition = 1,
}
| name | type | description |
|---|---|---|
type | UpgradePurposeType | Type of upgrade purpose. |
data | One of ConsensusParameters, StateTransition | Upgrade purposes. |
Transaction is invalid if:
typeis not validUpgradePurposeTypevalue`
ConsensusParameters
| name | type | description |
|---|---|---|
witnessIndex | uint16 | Index of witness that contains a serialized(with postcard) consensus parameters. |
checksum | byte[32] | The hash of the serialized consensus parameters. |
Given helper deserialize_consensus_parameters() that deserializes the consensus parameters from a witness by using postcard algorithm.
Transaction is invalid if:
witnessIndex >= tx.witnessesCountchecksum != sha256(tx.data.witnesses[witnessIndex])deserialize_consensus_parameters(tx.data.witnesses[witnessIndex])returns an error.
StateTransition
| name | type | description |
|---|---|---|
bytecodeRoot | byte[32] | The root of the new bytecode of the state transition function. |
Policy
// index using powers of 2 for efficient bitmasking
enum PolicyType : uint32 {
Tip = 1,
WitnessLimit = 2,
Maturity = 4,
MaxFee = 8,
}
| name | type | description |
|---|---|---|
data | One of Tip, WitnessLimit, or Maturity | Policy data. |
Tip
| name | type | description |
|---|---|---|
tip | uint64 | Additional, optional fee in BASE_ASSET to incentivize block producer to include transaction |
WitnessLimit
| name | type | description |
|---|---|---|
witnessLimit | uint64 | The maximum amount of witness data allowed for the transaction |
Given helper len() that returns the number of bytes of a field.
Transaction is invalid if:
len(tx.witnesses) > witnessLimit
Maturity
| name | type | description |
|---|---|---|
maturity | uint32 | Block until which the transaction cannot be included. |
Transaction is invalid if:
blockheight() < maturity
MaxFee
| name | type | description |
|---|---|---|
max_fee | uint64 | Required policy to specify the maximum fee payable by this transaction using BASE_ASSET. This is used to check transactions before the actual gas_price is known. |
Transaction is invalid if:
max_fee > sum_inputs(tx, BASE_ASSET_ID) - sum_outputs(tx, BASE_ASSET_ID)max_fee < max_fee(tx, BASE_ASSET_ID, gas_price)
Input
enum InputType : uint8 {
Coin = 0,
Contract = 1,
Message = 2,
}
| name | type | description |
|---|---|---|
type | InputType | Type of input. |
data | One of InputCoin, InputContract, or InputMessage | Input data. |
Transaction is invalid if:
type > InputType.Message
InputCoin
| name | type | description |
|---|---|---|
txID | byte[32] | Hash of transaction. |
outputIndex | uint16 | Index of transaction output. |
owner | byte[32] | Owning address or predicate root. |
amount | uint64 | Amount of coins. |
asset_id | byte[32] | Asset ID of the coins. |
txPointer | TXPointer | Points to the TX whose output is being spent. |
witnessIndex | uint16 | Index of witness that authorizes spending the coin. |
predicateGasUsed | uint64 | Gas used by predicate. |
predicateLength | uint64 | Length of predicate, in instructions. |
predicateDataLength | uint64 | Length of predicate input data, in bytes. |
predicate | byte[] | Predicate bytecode. |
predicateData | byte[] | Predicate input data (parameters). |
Given helper len() that returns the number of bytes of a field.
Transaction is invalid if:
witnessIndex >= tx.witnessesCountpredicateLength > MAX_PREDICATE_LENGTHpredicateDataLength > MAX_PREDICATE_DATA_LENGTH- If
predicateLength > 0; the computed predicate root (see below) is not equalowner predicateLength * 4 != len(predicate)predicateDataLength != len(predicateData)predicateGasUsed > MAX_GAS_PER_PREDICATE
Note: when signing a transaction,
txPointerandpredicateGasUsedare set to zero.Note: when verifying and estimating a predicate or executing a script,
txPointerandpredicateGasUsedare initialized to zero.
The predicate root is computed here.
InputContract
| name | type | description |
|---|---|---|
txID | byte[32] | Hash of transaction. |
outputIndex | uint16 | Index of transaction output. |
balanceRoot | byte[32] | Root of amount of coins owned by contract before transaction execution. |
stateRoot | byte[32] | State root of contract before transaction execution. |
txPointer | TXPointer | Points to the TX whose output is being spent. |
contractID | byte[32] | Contract ID. |
Transaction is invalid if:
- there is not exactly one output of type
OutputType.ContractwithinputIndexequal to this input's index
Note: when signing a transaction,
txID,outputIndex,balanceRoot,stateRoot, andtxPointerare set to zero.Note: when verifying a predicate or executing a script,
txID,outputIndex,balanceRoot,stateRoot, andtxPointerare initialized to zero.
InputMessage
| name | type | description |
|---|---|---|
sender | byte[32] | The address of the message sender. |
recipient | byte[32] | The address or predicate root of the message recipient. |
amount | uint64 | Amount of base asset coins sent with message. |
nonce | byte[32] | The message nonce. |
witnessIndex | uint16 | Index of witness that authorizes spending the coin. |
predicateGasUsed | uint64 | Gas used by predicate execution. |
dataLength | uint64 | Length of message data, in bytes. |
predicateLength | uint64 | Length of predicate, in instructions. |
predicateDataLength | uint64 | Length of predicate input data, in bytes. |
data | byte[] | The message data. |
predicate | byte[] | Predicate bytecode. |
predicateData | byte[] | Predicate input data (parameters). |
Given helper len() that returns the number of bytes of a field.
Transaction is invalid if:
witnessIndex >= tx.witnessesCountdataLength > MAX_MESSAGE_DATA_LENGTHpredicateLength > MAX_PREDICATE_LENGTHpredicateDataLength > MAX_PREDICATE_DATA_LENGTH- If
predicateLength > 0; the computed predicate root (see below) is not equalrecipient dataLength != len(data)predicateLength * 4 != len(predicate)predicateDataLength != len(predicateData)predicateGasUsed > MAX_GAS_PER_PREDICATE
The predicate root is computed here.
Note:
InputMessageswith data length greater than zero are not considered spent until they are included in a transaction of typeTransactionType.Scriptwith aScriptResultreceipt whereresultis equal to0indicating a successful script exitNote: when signing a transaction,
predicateGasUsedis set to zero.Note: when verifying and estimating a predicate,
predicateGasUsedis initialized to zero.
Output
enum OutputType : uint8 {
Coin = 0,
Contract = 1,
Change = 2,
Variable = 3,
ContractCreated = 4,
}
| name | type | description |
|---|---|---|
type | OutputType | Type of output. |
data | One of OutputCoin, OutputContract, OutputChange, OutputVariable, or OutputContractCreated. | Output data. |
Transaction is invalid if:
type > OutputType.ContractCreated
OutputCoin
| name | type | description |
|---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
OutputContract
| name | type | description |
|---|---|---|
inputIndex | uint16 | Index of input contract. |
balanceRoot | byte[32] | Root of amount of coins owned by contract after transaction execution. |
stateRoot | byte[32] | State root of contract after transaction execution. |
Transaction is invalid if:
inputIndex >= tx.inputsCounttx.inputs[inputIndex].type != InputType.Contract
Note: when signing a transaction,
balanceRootandstateRootare set to zero.Note: when verifying a predicate or executing a script,
balanceRootandstateRootare initialized to zero.
The balance root balanceRoot is the root of the SMT of balance leaves. Each balance is a uint64, keyed by asset ID (a byte[32]).
The state root stateRoot is the root of the SMT of storage slots. Each storage slot is a byte[32], keyed by a byte[32].
OutputChange
| name | type | description |
|---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
Transaction is invalid if:
- any other output has type
OutputType.OutputChangeand asset IDasset_id(i.e. only one change output per asset ID is allowed)
Note: when signing a transaction,
amountis set to zero.Note: when verifying a predicate or executing a script,
amountis initialized to zero.
This output type indicates that the output's amount may vary based on transaction execution, but is otherwise identical to a Coin output. An amount of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.
OutputVariable
| name | type | description |
|---|---|---|
to | byte[32] | Receiving address or predicate root. |
amount | uint64 | Amount of coins to send. |
asset_id | byte[32] | Asset ID of coins. |
Note: when signing a transaction,
to,amount, andasset_idare set to zero.Note: when verifying a predicate or executing a script,
to,amount, andasset_idare initialized to zero.
This output type indicates that the output's amount and owner may vary based on transaction execution, but is otherwise identical to a Coin output. An amount of zero after transaction execution indicates that the output is unspendable and can be pruned from the UTXO set.
OutputContractCreated
| name | type | description |
|---|---|---|
contractID | byte[32] | Contract ID. |
stateRoot | byte[32] | Initial state root of contract. |
Witness
| name | type | description |
|---|---|---|
dataLength | uint64 | Length of witness data, in bytes. |
data | byte[] | Witness data. |
TXPointer
The location of the transaction in the block. It can be used by UTXOs as a reference to the transaction or by the transaction itself to make it unique.
| name | type | description |
|---|---|---|
blockHeight | uint32 | Block height. |
txIndex | uint16 | Transaction index. |
Identifiers
This chapter defines how to compute unique identifiers.
Asset ID
The asset ID (also called asset hash) of a asset is computed as
the hash of the CONTRACT_ID and a 256-bit SUB_IDENTIFIER.
sha256(CONTRACT_ID ++ SUB_IDENTIFIER)
Blob ID
The blob ID (also called blob hash) of a transaction is computed as the hash of the blob data.
Blob ID calculation doesn't vary between chains.
sha256(blob_data)
Contract ID
For a transaction of type TransactionType.Create, tx, the contract ID is
sha256(0x4655454C ++ tx.data.salt ++ root(tx.data.witnesses[bytecodeWitnessIndex].data) ++ root_smt(tx.storageSlots)),
where root is the Merkle root of the binary Merkle tree with
each leaf being 16KiB of instructions, and root_smt is the
Sparse Merkle tree root of the provided key-value pairs.
If the bytecode is not a multiple of 16 KiB, the final leaf should be zero-padded rounding up to the nearest multiple
of 8 bytes.
Predicate ID
For an input of type InputType.Coin or InputType.Message, input, the predicate owner is calculated as:
sha256(0x4655454C ++ root(input.predicate)), where root is the Merkle root of
the binary Merkle tree each leaf being 16KiB of instructions.
If the bytecode is not a multiple of 16 KiB, the final leaf should be zero-padded rounding up to the nearest multiple of 8 bytes.
Transaction ID
The transaction ID (also called transaction hash) of a transaction is computed as
the hash of CHAIN_ID and the
serialized transaction with fields zeroed out for signing
(see different inputs and outputs for which fields are set to zero), and without witness data. In other words, only
all non-witness data is hashed.
sha256(CHAIN_ID ++ serialized_tx(tx))
UTXO ID
Coin ID
Is represented as an outpoint: a pair of transaction ID as byte[32] and output index as a uint16.
Message ID
The ID of a message is computed as the hash of:
- the sender address as
byte[32], - the recipient address as
byte[32], - the Message nonce as
byte[32], - the amount being sent with the message as
uint64, - the message data as
byte[]
hash(byte[32] ++ byte[32] ++ byte[32] ++ uint64 ++ byte[]). The address values are serialized as a byte array of length 32 left-padded with zeroes, and all other value types are serialized according to the standard transaction serialization. Note that the message data length is not included since there is only one dynamically sized field and can be implicitly determined by the hash preimage size.
Message Nonce
The nonce value for InputMessage is determined by the sending system and is published at the time the message is sent. The nonce value for OutputMessage is computed as the hash of the Transaction ID that emitted the message and the index of the message receipt uint16 (with canonical encoding): hash(byte[32] ++ canonical(uint16)).
Fee ID
The UTXO ID of collected fees in a block is the block height as a 32-byte big-endian unsigned integer (i.e. the first byte of the 32-byte array is the most significant byte, and so on).
Protocol
- Transaction Validity
- Cryptographic Primitives
- Storage Slot Initialization
- Block Header Format
- Relayer/Bridge
Transaction Validity
- Transaction Life Cycle
- Access Lists
- VM Precondition Validity Rules
- Predicate Verification
- Script Execution
- VM Postcondition Validity Rules
Transaction Life Cycle
Once a transaction is seen, it goes through several stages of validation, in this order:
Access Lists
The validity rules below assume sequential transaction validation for side effects (i.e. state changes). However, by construction, transactions with disjoint write access lists can be validated in parallel, including with overlapping read-only access lists. Transactions with overlapping write access lists must be validated and placed in blocks in topological order.
UTXOs and contracts in the read-only and write-destroy access lists must exist (i.e. have been created previously) in order for a transaction to be valid. In other words, for a unique state element ID, the write-create must precede the write-destroy.
Read-only access list:
Write-destroy access list:
- For each input
InputType.Coin- The UTXO ID
(txId, outputIndex)
- The UTXO ID
- For each input
InputType.Contract- The UTXO ID
(txId, outputIndex)
- The UTXO ID
- For each input
InputType.Message- The message ID
messageID
- The message ID
Write-create access list:
- For each output
OutputType.ContractCreated- The contract ID
contractID
- The contract ID
- For each output
- The created UTXO ID
Note that block proposers use the contract ID contractID for inputs and outputs of type InputType.Contract and OutputType.Contract rather than the pair of txId and outputIndex.
VM Precondition Validity Rules
This section defines VM precondition validity rules for transactions: the bare minimum required to accept an unconfirmed transaction into a mempool, and preconditions that the VM assumes to hold prior to execution. Chains of unconfirmed transactions are omitted.
For a transaction tx, UTXO set state, contract set contracts, and message set messages, the following checks must pass.
Note:
InputMessageswhereinput.dataLength > 0are not dropped from themessagesmessage set until they are included in a transaction of typeTransactionType.Scriptwith aScriptResultreceipt whereresultis equal to0indicating a successful script exit.
Base Sanity Checks
Base sanity checks are defined in the transaction format.
Spending UTXOs and Created Contracts
for input in tx.inputs:
if input.type == InputType.Contract:
if not input.contractID in contracts:
return False
elif input.type == InputType.Message:
if not input.nonce in messages:
return False
else:
if not (input.txId, input.outputIndex) in state:
return False
return True
If this check passes, the UTXO ID (txId, outputIndex) fields of each contract input is set to the UTXO ID of the respective contract. The txPointer of each input is also set to the TX pointer of the UTXO with ID utxoID.
Sufficient Balance
For each asset ID assetId in the input and output set:
def gas_to_fee(gas, gasPrice) -> int:
"""
Converts gas units into a fee amount
"""
return ceil(gas * gasPrice / GAS_PRICE_FACTOR)
def sum_data_messages(tx, assetId) -> int:
"""
Returns the total balance available from messages containing data
"""
total: int = 0
if assetId == 0:
for input in tx.inputs:
if input.type == InputType.Message and input.dataLength > 0:
total += input.amount
return total
def sum_inputs(tx, assetId) -> int:
total: int = 0
for input in tx.inputs:
if input.type == InputType.Coin and input.assetId == assetId:
total += input.amount
elif input.type == InputType.Message and assetId == 0 and input.dataLength == 0:
total += input.amount
return total
def transaction_size_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of a transaction based on size in bytes
"""
return size(tx) * GAS_PER_BYTE
def minted(tx, assetId) -> int:
"""
Returns any minted amounts by the transaction
"""
if tx.type != TransactionType.Mint or assetId != tx.mintAssetId:
return 0
return tx.mint_amount
def sum_outputs(tx, assetId) -> int:
total: int = 0
for output in tx.outputs:
if output.type == OutputType.Coin and output.assetId == assetId:
total += output.amount
return total
def input_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of verifying input utxos
"""
total: int = 0
witnessIndices = set()
for input in tx.inputs:
if input.type == InputType.Coin or input.type == InputType.Message:
# add fees allocated for predicate execution
if input.predicateLength == 0:
# notate witness index if input is signed
witnessIndices.add(input.witnessIndex)
else:
# add intrinsic gas cost of predicate merkleization based on number of predicate bytes
total += contract_code_root_gas_fee(input.predicateLength)
total += input.predicateGasUsed
# add intrinsic cost of vm initialization
total += vm_initialization_gas_fee()
# add intrinsic cost of verifying witness signatures
total += len(witnessIndices) * eck1_recover_gas_fee()
return total
def metadata_gas_fees(tx) -> int:
"""
Computes the intrinsic gas cost of processing transaction outputs
The `contract_code_root_gas_fee`, `sha256_gas_fee`, and `contract_state_root_gas_fee`
are based on the benchmarked gas costs of these operations.
Consensus parameters contain definitions of gas costs for all operations and opcodes in the network.
"""
total: int = 0
if tx.type == TransactionType.Create:
for output in tx.outputs:
if output.type == OutputType.OutputContractCreated:
# add intrinsic cost of calculating the code root based on the size of the contract bytecode
total += contract_code_root_gas_fee(tx.witnesses[tx.bytecodeWitnessIndex].dataLength)
# add intrinsic cost of calculating the state root based on the number of sotrage slots
total += contract_state_root_gas_fee(tx.storageSlotCount)
# add intrinsic cost of calculating the contract id
# size = 4 byte seed + 32 byte salt + 32 byte code root + 32 byte state root
total += sha256_gas_fee(100)
elif tx.type == TransactionType.Upgrade:
if tx.upgradePurpose.type == UpgradePurposeType.ConsensusParameters:
# add intrinsic cost of calculating the consensus parameters hash
total += sha256_gas_fee(size(tx.witnesses[tx.upgradePurpose.witnessIndex].data))
elif tx.type == TransactionType.Upload:
# add intrinsic cost of calculating the root based on the number of bytecode subsections
total += contract_state_root_gas_fee(tx.subsectionsNumber)
# add intrinsic cost of hashing the subsection for verification of the connection with Binary Merkle tree root
total += sha256_gas_fee(size(tx.witnesses[tx.witnessIndex]))
if tx.type != TransactionType.Mint:
# add intrinsic cost of calculating the transaction id
total += sha256_gas_fee(size(tx))
return total
def intrinsic_gas_fees(tx) -> int:
"""
Computes intrinsic costs for a transaction
"""
fees: int = 0
# add the cost of initializing a vm for the script
if tx.type == TransactionType.Create or tx.type == TransactionType.Script:
fees += vm_initialization_gas_fee()
fees += metadata_gas_fees(tx)
fees += intrinsic_input_gas_fees(tx)
return fees
def min_gas(tx) -> int:
"""
Comutes the minimum amount of gas required for a transaction to begin processing.
"""
gas = transaction_size_gas_fees(tx) + intrinsic_gas_fees(tx)
if tx.type == TransactionType.Upload
# charge additionally for storing bytecode on chain
gas += transaction_size_gas_fees(size(tx.witnesses[tx.witnessIndex]))
return gas
def max_gas(tx) -> int:
"""
Computes the amount of gas required to process a transaction.
"""
gas = min_gas(tx)
gas = gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE
if tx.type == TransactionType.Script:
gas += tx.gasLimit
return gas
def maxFee(tx, assetId, gasPrice) -> int:
"""
Computes the maximum potential amount of fees that may need to be charged to process a transaction.
"""
maxGas = max_gas(tx)
feeBalance = gas_to_fee(maxGas, gasPrice)
# Only base asset can be used to pay for gas
if assetId == 0:
return feeBalance
else:
return 0
def available_balance(tx, assetId) -> int:
"""
Make the data message balance available to the script
"""
availableBalance = sum_inputs(tx, assetId) + sum_data_messages(tx, assetId) + minted(tx, assetId)
return availableBalance
def unavailable_balance(tx, assetId) -> int:
sentBalance = sum_outputs(tx, assetId)
# Total fee balance
feeBalance = tx.policies.max_fee
# Only base asset can be used to pay for gas
if assetId == 0:
return sentBalance + feeBalance
return sentBalance
# The sum_data_messages total is not included in the unavailable_balance since it is spendable as long as there
# is enough base asset amount to cover gas costs without using data messages. Messages containing data can't
# cover gas costs since they are retryable.
return available_balance(tx, assetId) >= (unavailable_balance(tx, assetId) + sum_data_messages(tx, assetId))
Valid Signatures
def address_from(pubkey: bytes) -> bytes:
return sha256(pubkey)[0:32]
for input in tx.inputs:
if (input.type == InputType.Coin or input.type == InputType.Message) and input.predicateLength == 0:
# ECDSA signatures must be 64 bytes
if tx.witnesses[input.witnessIndex].dataLength != 64:
return False
# Signature must be from owner
if address_from(ecrecover_k1(txhash(), tx.witnesses[input.witnessIndex].data)) != input.owner:
return False
return True
Signatures and signature verification are specified here.
The transaction hash is computed as defined here.
Predicate Verification
For each input of type InputType.Coin or InputType.Message, and predicateLength > 0, verify its predicate.
Script Execution
Given transaction tx, the following checks must pass:
If tx.scriptLength == 0, there is no script and the transaction defines a simple balance transfer, so no further checks are required.
If tx.scriptLength > 0, the script must be executed. For each asset ID assetId in the input set, the free balance available to be moved around by the script and called contracts is freeBalance[assetId]. The initial message balance available to be moved around by the script and called contracts is messageBalance:
freeBalance[assetId] = available_balance(tx, assetId) - unavailable_balance(tx, assetId)
messageBalance = sum_data_messages(tx, 0)
Once the free balances are computed, the script is executed. After execution, the following is extracted:
- The transaction in-memory on VM termination is used as the final transaction which is included in the block.
- The unspent free balance
unspentBalancefor each asset ID. - The unspent gas
unspentGasfrom the$ggasregister.
size(tx) encompasses the entire transaction serialized according to the transaction format, including witness data.
This ensures every byte of block space either on Fuel or corresponding DA layer can be accounted for.
If the transaction as included in a block does not match this final transaction, the block is invalid.
Fees
The cost of a transaction can be described by:
def cost(tx, gasPrice) -> int:
return gas_to_fee(min_gas(tx) + tx.gasLimit - unspentGas, gasPrice)
where:
min_gas(tx)is the minimum cost of the transaction in gas, including intrinsic gas fees incurred from:- The number of bytes comprising the transaction
- Processing inputs, including predicates
- Processing outputs
- VM initialization
unspentGasis the amount gas left over after intrinsic fees and execution of the transaction, extracted from the$ggasregister. Converting unspent gas to a fee describes how much "change" is left over from the user's payment; the block producer collects this unspent gas as reward.gas_to_feeis a function that converts gas to a concrete fee based on a given gas price.
Fees incurred by transaction processing outside the context of execution are collectively referred to as intrinsic fees. Intrinsic fees include the cost of storing the transaction, calculated on a per-byte basis, the cost of processing inputs and outputs, including predicates and signature verification, and initialization of the VM prior to any predicate or script execution. Because intrinsic fees are independent of execution, they can be determined a priori and represent the bare minimum cost of the transaction.
A naturally occurring result of a variable gas limit is the concept of minimum and maximum fees. The minimum fee is, thus, the exact fee required to pay the fee balance, while the maximum fee is the minimum fee plus the gas limit:
min_gas = min_gas(tx)
max_gas = min_gas + (tx.witnessBytesLimit - tx.witnessBytes) * GAS_PER_BYTE + tx.gasLimit
min_fee = gas_to_fee(min_gas, gasPrice)
max_fee = gas_to_fee(max_gas, gasPrice)
The cost of the transaction cost(tx) must lie within the range defined by [min_fee, max_fee]. min_gas is defined as the sum of all intrinsic costs of the transaction known prior to execution. The definition of max_gas illustrates that the delta between minimum gas and maximum gas is the sum of:
- The remaining allocation of witness bytes, converted to gas
- The user-defined
tx.gasLimit
Note that gasLimit applies to transactions of type Script. gasLimit is not applicable for transactions of type Create and is defined to equal 0 in the above formula.
A transaction cost cost(tx), in gas, greater than max_gas is invalid and must be rejected; this signifies that the user must provide a higher gas limit for the given transaction. min_fee is the minimum reward the producer is guaranteed to collect, and max_fee is the maximum reward the producer is potentially eligible to collect. In practice, the user is always charged intrinsic fees; thus, unspentGas is the remainder of max_gas after intrinsic fees and the variable cost of execution. Calculating a conversion from unspentGas to an unspent fee describes the reward the producer will collect in addition to min_fee.
VM Postcondition Validity Rules
This section defines VM postcondition validity rules for transactions: the requirements for a transaction to be valid after it has been executed.
Given transaction tx, state state, and contract set contracts, the following checks must pass.
Correct Change
If change outputs are present, they must have:
- if the transaction does not revert;
- if the asset ID is
0; anamountofunspentBalance + floor((unspentGas * gasPrice) / GAS_PRICE_FACTOR) - otherwise; an
amountof the unspent free balance for that asset ID after VM execution is complete
- if the asset ID is
- if the transaction reverts;
- if the asset ID is
0; anamountof the initial free balance plus(unspentGas * gasPrice) - messageBalance - otherwise; an
amountof the initial free balance for that asset ID.
- if the asset ID is
State Changes
Transaction processing is completed by removing spent UTXOs from the state and adding created UTXOs to the state.
Coinbase Transaction
The coinbase transaction is a mechanism for block creators to collect transaction fees.
In order for a coinbase transaction to be valid:
- It must be a Mint transaction.
- The coinbase transaction must be the last transaction within a block, even if there are no other transactions in the block and the fee is zero.
- The
mintAmountdoesn't exceed the total amount of fees processed from all other transactions within the same block. - The
mintAssetIdmatches theassetIdthat fees are paid in (assetId == 0).
The minted amount of the coinbase transaction intrinsically increases the balance corresponding to the inputContract.
This means the balance of mintAssetId is directly increased by mintAmount on the input contract,
without requiring any VM execution. Compared to coin outputs, intrinsically increasing a contract balance to collect
coinbase amounts prevents the accumulation of dust during low-usage periods.
Cryptographic Primitives
Hashing
All hashing is done with SHA-2-256 (also known as SHA-256), defined in FIPS 180-4.
HashDigest
Output of the hashing function. Exactly 256 bits (32 bytes) long.
Merkle Trees
Two Merkle tree structures are used: a Binary Merkle Tree (to commit to bytecode) and a Sparse Merkle Tree (to commit to contract storage, i.e. state).
Binary Merkle Tree
Binary Merkle trees are constructed in the same fashion as described in Certificate Transparency (RFC-6962), except for using a different hashing function. Leaves are hashed once to get leaf node values and internal node values are the hash of the concatenation of their children (either leaf nodes or other internal nodes).
Nodes contain a single field:
| name | type | description |
|---|---|---|
v | HashDigest | Node value. |
The base case (an empty tree) is defined as the hash of the empty string:
node.v = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
For leaf node node of leaf data d:
node.v = h(0x00, serialize(d))
For internal node node with children l and r:
node.v = h(0x01, l.v, r.v)
Note that rather than duplicating the last node if there are an odd number of nodes (the Bitcoin design), trees are allowed to be imbalanced. In other words, the height of each leaf may be different. For an example, see Section 2.1.3 of Certificate Transparency (RFC-6962).
Leaves and internal nodes are hashed differently: the one-byte 0x00 is prepended for leaf nodes while 0x01 is prepended for internal nodes. This avoids a second-preimage attack where internal nodes are presented as leaves trees with leaves at different heights.
Binary Merkle Tree Inclusion Proofs
| name | type | description |
|---|---|---|
root | HashDigest[] | The expected root of the Merkle tree. |
data | Bytes | The data of the leaf (unhashed). |
siblings | HashDigest[] | Sibling hash values, ordered starting from the leaf's neighbor. |
A proof for a leaf in a binary Merkle tree, as per Section 2.1.1 of Certificate Transparency (RFC-6962).
In some contexts, the array of sibling hashes is also known as the proof set. Note that proof format prescribes that leaf data be in its original, unhashed state, while the proof set (array of sibling data) uses hashed data. This format precludes the proof set from itself including the leaf data from the leaf undergoing the proof; rather, proof verification explicitly requires hashing the leaf data during the calculation of the proof set root.
Sparse Merkle Tree
Sparse Merkle Trees (SMTs) are sparse, i.e. they contain mostly empty leaves. They can be used as key-value stores for arbitrary data, as each leaf is keyed by its index in the tree. Storage efficiency is achieved through clever use of implicit defaults, avoiding the need to store empty leaves.
Additional rules are added on top of plain binary Merkle trees:
- Default values are given to leaf nodes with empty leaves.
- While the above rule is sufficient to pre-compute the values of intermediate nodes that are roots of empty subtrees, a further simplification is to extend this default value to all nodes that are roots of empty subtrees. The 32-byte zero, i.e.
0x0000000000000000000000000000000000000000000000000000000000000000, is used as the default value. This rule takes precedence over the above one. - The number of hashing operations can be reduced to be logarithmic in the number of non-empty leaves on average, assuming a uniform distribution of non-empty leaf keys. An internal node that is the root of a subtree that contains exactly one non-empty leaf is replaced by that leaf's leaf node.
Nodes contain a single field:
| name | type | description |
|---|---|---|
v | HashDigest | Node value. |
In the base case, where a sparse Merkle tree has height = 0, the root of a tree is defined as the hash of the empty string:
node.v = 0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
When a sparse Merkle tree has a height of 0, it can have no leaves, and, therefore, no default value children. The root is then calculated as the hash of the empty string, similar to that of an empty binary Merkle tree.
For a tree with height > 0, the root of an empty tree is defined as the default value:
node.v = 0x0000000000000000000000000000000000000000000000000000000000000000
Note that this is in contrast to the base case of the sparse and binary Merkle trees, where the root is the hash of the empty string. When a sparse Merkle tree has a height greater than 0, a new tree instance is composed of default value leaves. Nodes containing only default value children have the default value as well. Applying these rules recursively percolates the default value up to the tree's root.
For leaf node node of leaf data d with key k:
node.v = h(0x00, k, h(serialize(d)))
The key of leaf nodes must be prepended, since the index of a leaf node that is not at maximum depth cannot be determined without this information. Leaf values are hashed so that they do not need to be included in full in non-membership proofs.
For internal node node with children l and r:
node.v = h(0x01, l.v, r.v)
Insertion
Before insertion of the key-value pair, each key of the Sparse Merkle Tree should be hashed with sha256 to prevent tree structure manipulations.
During the proof verification, the original leaf key should be hashed similarly. Otherwise, the root will not match.
Sparse Merkle Tree Inclusion Proofs
SMTs can further be extended with compact proofs. Merkle proofs are composed, among other things, of a list of sibling node values. We note that, since nodes that are roots of empty subtrees have known values (the default value), these values do not need to be provided explicitly; it is sufficient to simply identify which siblings in the Merkle branch are roots of empty subtrees, which can be done with one bit per sibling.
For a Merkle branch of height h, an h-bit value is appended to the proof. The lowest bit corresponds to the sibling of the leaf node, and each higher bit corresponds to the next parent. A value of 1 indicates that the next value in the list of values provided explicitly in the proof should be used, and a value of 0 indicates that the default value should be used.
A proof into an SMT is structured as:
| name | type | description |
|---|---|---|
depth | uint16 | Depth of the leaf node. The root node is at depth 0. Must be <= 256. |
siblings | HashDigest[] | Sibling hash values, ordered starting from the leaf's neighbor. |
includedSiblings | byte[32] | Bitfield of explicitly included sibling hashes. |
The includedSiblings is ordered by most-significant-byte first, with each byte ordered by most-significant-bit first. The lowest bit corresponds to the leaf node level.
A specification describing a suite of test vectors and outputs of a Sparse Merkle Tree is here.
ECDSA Public-Key Cryptography
Consensus-critical data is authenticated using ECDSA, with the curve secp256k1. A highly-optimized library is available in C, with wrappers in Go and Rust.
Public keys are encoded in uncompressed form, as the concatenation of the x and y values. No prefix is needed to distinguish between encoding schemes as this is the only encoding supported.
Deterministic signatures (RFC-6979) should be used when signing, but this is not enforced at the protocol level as it cannot be.
Signatures are represented as the r and s (each 32 bytes), and v (1-bit) values of the signature. r and s take on their usual meaning (see: SEC 1, 4.1.3 Signing Operation), while v is used for recovering the public key from a signature more quickly (see: SEC 1, 4.1.6 Public Key Recovery Operation). Only low-s values in signatures are valid (i.e. s <= secp256k1.n//2); s can be replaced with -s mod secp256k1.n during the signing process if it is high. Given this, the first bit of s will always be 0, and can be used to store the 1-bit v value.
v represents the parity of the Y component of the point, 0 for even and 1 for odd. The X component of the point is assumed to always be low, since the possibility of it being high is negligible.
Putting it all together, the encoding for signatures is:
| 32 bytes || 32 bytes |
[256-bit r value][1-bit v value][255-bit s value]
This encoding scheme is derived from EIP 2098: Compact Signature Representation.
EdDSA Public-Key Cryptography
Ed25519 is supported for use by applications built on Fuel. Edwards curve operations are performed by the ed25519-dalek Rust library.
Public keys are encoded in compressed form as specified by the Ed25519 format RFC-8032 5.1.5. Point compression is performed by replacing the most significant bit in the final octet of the y coordinate with the sign bit from the x coordinate:
#![allow(unused)] fn main() { let mut pk = y; pk ^= x.is_negative().unwrap_u8() << 7; }
Public keys are required to be strong enough to prevent malleability, and are checked for weakness during signature verification.
Signatures are 64 bytes, represented as the concatenation of R (32 bytes) and S (32 bytes) Where R and S are defined in RFC-8032 5.1.6.
Signatures must conform to strict verification requirements to avoid malleability concerns. While this is not part of the original Ed25519 specification, it has become a growing concern especially in cryptocurrency applications.
JSON Format for Contract Storage Initializers
Contracts can request that certain storage slots are initialized to specific values. These initialized slots are represented in JSON format as an array where each element represents a storage slot and has the following properties:
"key": String, a 32-byte key for a given storage slot;"value": String, a 32-byte value that initializes the slot;
For instance, the following is a JSON object that requests that the 3 storage slots with keys 0x11..11, 0x22..22, and 0x33..33, are respectively initialized to the values indicated.
[
{
"key": "0x1111111111111111111111111111111111111111111111111111111111111111",
"value": "0x1010101010101010101010101010101010101010101010101010101010101010"
},
{
"key": "0x2222222222222222222222222222222222222222222222222222222222222222",
"value": "0x2020202020202020202020202020202020202020202020202020202020202020"
},
{
"key": "0x3333333333333333333333333333333333333333333333333333333333333333",
"value": "0x0303030303030303030303030303030303030303030303030303030303030303"
},
]
Block Header
Application Header
The application header is a network-agnostic block header. Different networks may wrap the application header in a consensus header, depending on their consensus protocol.
| name | type | description |
|---|---|---|
da_height | uint64 | Height of the data availability layer up to which (inclusive) input messages are processed. |
consensusParametersVersion | uint32 | The version of the consensus parameters used to execute this block. |
stateTransitionBytecodeVersion | uint32 | The version of the state transition bytecode used to execute this block. |
txCount | uint16 | Number of transactions in this block. |
message_receipt_count | uint32 | Number of output messages in this block. |
txRoot | byte[32] | Merkle root of transactions in this block. |
message_outbox_root | byte[32] | Merkle root of output messages messageId in this block. |
event_inbox_root | byte[32] | Merkle root of all events imported from L1 in this block. The order of the events added to the Merkle tree is the L1 block order, and the index of each event within each block |
Layer 1 Relayer/Bridge Protocol
The Fuel relayer/bridge protocol is a set of rules that govern the interaction between the Fuel blockchain and the Layer 1 (L1) blockchain (e.g. Ethereum).
The Fuel blockchain can emit messages that will be processed by the smart contract on the L1 blockchain. The smart contract on the L1 can also emit events that will be processed by the Fuel blockchain. This is used to move any data between the L1 blockchain and the Fuel blockchain.
Fuel Message Outbox
The message outbox is the set of messages sent to the L1 blockchain from the Fuel blockchain.
Fuel Event Inbox
The event inbox is the set of events received from the L1 blockchain by the Fuel blockchain.
The block producer will receive a list of events from the L1 by some relayer, and then include the merkle root of the events in the block header.
There are two types of events that can be received from the L1:
- Messages
- Transactions
Messages
An arbitrary message sent from the L1 to the Fuel blockchain. This can be used to move assets from the L1 to the Fuel blockchain or send other arbitrary information to the Fuel blockchain.
| name | type | description |
|---|---|---|
sender | bytes[32] | The identity of the sender of the message on the L1 |
recipient | bytes[32] | The recipient of the message on the Fuel Blockchain |
nonce | bytes[32] | Unique identifier of the message assigned by the L1 contract |
amount | uint64 | The amount of the base asset transfer |
data | byte[] | Arbitrary message data |
Transactions
These are transactions that are submitted on the L1 that must be executed on the Fuel blockchain. This "Forced Transaction Inclusion" is a security feature that allows participants of the Fuel Blockchain to access their funds in the (unlikely) event that the Fuel blockchain block production is compromised or malicious, e.g. the block producer is censoring transactions.
| name | type | description |
|---|---|---|
nonce | bytes[32] | Unique identifier of the transaction assigned by the L1 contract |
max_gas | uint64 | The maximum amount of gas allowed to use on Fuel Blockchain |
serialized_transaction | byte[] | The serialized transaction bytes following canonical serialization |
The serialized_transaction can be any transaction variant except the Mint transaction, which
is only ever created by the block producer. Mint transactions will be rejected by the Fuel blockchain if they are relayed
from the L1.
Ordering
It is important that the L1 events are ordered correctly when they are relayed to the Fuel blockchain. The events will be ordered by the L1 block height and then by the index of the event within the block.
The order is important because a merkle root will be generated each time events from L1 are included in a Fuel block. This merkle root can later be used to prove that an arbitrary event was included on that block without having to store every event on the block header explicitly. Just the merkle root will be on the block header. The order of the events affects the value of the merkle root.
Application Binary Interface (ABI)
This document describes and specifies the ABI (Application Binary Interface) of the FuelVM, the Sway programming language, and contracts written in Sway.
JSON ABI Format
The JSON of an ABI is the human-readable representation of the interface of a Sway contract.
Spec Version
Current specVersion is 1.0
The version above should be updated each time this spec changes and the JSON ABI generator should be updated with the new changes along with an increment in the spec version.
Notation
Before describing the format of the JSON ABI, we provide some definitions that will make the JSON ABI spec easier to read.
Given the example below:
#![allow(unused)] fn main() { struct Foo { x: bool } struct Bar<T> { y: T } fn baz(input1: Foo, input2: Bar<u64>); // an ABI function }
we define the following expressions:
- type concrete declaration: the declaration or definition of a type which can be generic.
struct Foo { .. }andstruct Bar<T> { .. }in the example above are both type declarations. Note that generic types may have multiple type concrete declaration. - type metadata declaration: the declaration or definition of a type which can be generic.
struct Foo { .. }andstruct Bar<T> { .. }in the example above are both type declarations. The metadata declaration contains component details about the type. And a single type metadata declaration is generated per type, even for generic types. - type application: the application or use of a type.
FooandBar<u64>infn baz(input1: Foo, input2: Bar<u64>);in the example above are both applications of the type declarationsstruct Foo { .. }andstruct Bar<T> { .. }respectively. - type parameter: a generic parameter used in a type declaration.
Tinstruct Bar<T>in the example above is a type parameter. - type argument: an application of a type parameter used in a type application.
u64ininput2: Bar<u64>in the example above is a type argument.
JSON ABI Spec
The ABI of a contract is represented as a JSON object containing the following properties:
"specVersion": a string representing the version pointing to this document versioning.specVersionenables the reader of the JSON ABI to find the correct specification for that file, this can be done by comparing it to value in spec version."encodingVersion": a string representing the version of theABIEncodeandABIDecodeused in this program."programType": a string that can be"script","contract","predicate","library". This is used by the SDK to generate types without having to manually specify the program type."concreteTypes": an array describing all the type concrete declarations used (or transitively used) in the ABI. Each type concrete declaration is a JSON object that contains the following properties:"type": a string representing the type, thesha256of this string generates theconcreteTypeId."concreteTypeId": a unique string hash based ID. Generated as specified in Hash Based Ids."metadataTypeId": the type metadata declaration ID of this type, if the type metadata has components or is generic, otherwise nonexistent."typeArguments": an array of type concrete declarations hash based IDs of the type parameters of the type, if the type is generic, otherwise nonexistent.
"metadataTypes": an array describing all the type metadata declarations used (or transitively used) in the ABI. Each type metadata declaration is a JSON object that contains the following properties:"type": a string representation of the type metadata declaration. The section JSON ABI Format for Each Possible Metadata Type Declaration specifies the format for each possible type."metadataTypeId": a unique integer ID."components": an array of the components of a given type, if any, otherwise nonexistent. Each component is a type application represented as a JSON object that contains the following properties:"name": the name of the component."typeId": the type metadata declaration ID (number) or type concrete declaration hash based ID (string) of the type of the component."typeArguments": an array of the type arguments used when applying the type of the component, if the type is generic, otherwise nonexistent. Each type argument is a type application represented as a JSON object that contains the following properties:"typeId": the type metadata declaration ID (number) or type concrete declaration hash based ID (string) of the type of the component."typeArguments": an array of the type arguments used when applying the type of the type argument, if the type is generic, otherwise nonexistent. The format of the elements of this array recursively follows the rules described in this section.
"typeParameters": an array of type metadata declaration ID of the type parameters of the type, if the type is generic, otherwise nonexistent. Each type parameter is a type declaration and is represented as described in Generic Type Parameter.
"functions": an array describing all the functions in the ABI. Each function is a JSON object that contains the following properties:"name": the name of the function"inputs": an array of objects that represents the inputs to the function (i.e. its parameters). Each input is a type application represented as a JSON object that contains the following properties:"name": the name of the input."concreteTypeId": the type concrete declaration hash based ID of the type of the input.
"output": the type concrete declaration hash based ID of the type being returned by the function."attributes": an optional array of attributes. Each attribute is explained in the dedicated section and is represented as a JSON object that contains the following properties:"name": the name of the attribute."arguments": an array of attribute arguments.
"loggedTypes": an array describing all instances oflogorlogdin the contract's bytecode. Each instance is a JSON object that contains the following properties:"logId": a string containing the 64bit hash based decimal ID calculated from the first 8 bytes of thesha256of a string that represents the type logged as defined in Hash Based Ids. Thelogandlogdinstructions must set their$rBregister to that ID."concreteTypeId": the type concrete declaration hash based ID of the value being logged.
"messagesTypes": an array describing all instances ofsmoin the contract's bytecode. Each instance is a JSON object that contains the following properties:"message_id": a unique string ID."concreteTypeId": the type concrete declaration hash based ID of the message data being sent.
"configurables": an array describing allconfigurablevariables used in the contract. Eachconfigurablevariable is represented as a JSON object that contains the following properties:"name": the name of theconfigurablevariable."concreteTypeId": the type concrete declaration hash based ID of the type of theconfigurablevariable."offset": the specific offset within the contract's bytecode, in bytes, to the data section entry for theconfigurablevariable.
Note: This JSON should be both human-readable and parsable by the tooling around the FuelVM and the Sway programming language. There is a detailed specification for the binary encoding backing this readable descriptor. The Function Selector Encoding section specifies the encoding for the function being selected to be executed and each of the argument types.
Attributes Semantics
| Attribute name | Attribute arguments | Semantics |
|---|---|---|
storage | read and/or write | Specifies if a function reads or writes to/from storage |
payable | None | Specifies if a function can accept coins: a function without payable attribute must not accept coins |
test | None | Specifies if a function is a unit test |
inline | never or always, but not both | Specifies if a function should be inlined during code generation |
doc-comment | String | Documentation comment |
doc | Not defined yet | Not defined yet |
A Simple Example
Below is a simple example showing how the JSON ABI for an example that does not use generic or complex types. We will later go over more complex examples.
#![allow(unused)] fn main() { abi MyContract { fn first_function(arg: u64) -> bool; fn second_function(arg: b256); } }
the JSON representation of this ABI looks like:
{
"concreteTypes": [
{
"type": "u64",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
},
{
"type": "b256",
"concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"
},
{
"type": "bool",
"concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903"
},
{
"type": "()",
"concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"
}
],
"metadataTypes": [],
"functions": [
{
"inputs": [
{
"name": "arg",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
}
],
"name": "first_function",
"output": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903"
},
{
"inputs": [
{
"name": "arg",
"concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"
}
],
"name": "second_function",
"output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"
}
],
"loggedTypes": []
}
JSON ABI Format for Each Possible Metadata Type Declaration
Below is a list of the JSON ABI formats for each possible metadata type declaration:
struct
{
"metadataTypeId": <id>,
"type": "struct <struct_name>",
"components": [
{
"name": "<field1_name>",
"typeId": "<field1_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
{
"name": "<field2_name>",
"typeId": "<field2_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
...
],
"typeParameters": [
<type_param1_type_id>,
<type_param2_type_id>,
...
]
}
enum
{
"metadataTypeId": <id>,
"type": "enum <enum_name>",
"components": [
{
"name": "<variant1_name>",
"typeId": "<variant1_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
{
"name": "<variant2_name>",
"typeId": "<variant2_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
...
],
"typeParameters": [
<type_param1_type_id>,
<type_param2_type_id>,
...
]
}
array
{
"metadataTypeId": <id>,
"type": "[_; <n>]",
"components": [
{
"name": "__array_element",
"typeId": "<element_type>",
"typeArguments": ...
}
{
"name": "__array_element",
"typeId": "<element_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
]
}
<n>is the size of the array.
tuple
{
"metadataTypeId": <id>,
"type": "(_, _, ...)",
"components": [
{
"name": "__tuple_element",
"typeId": "<field1_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
{
"name": "__tuple_element",
"typeId": "<field2_type_id>",
"typeArguments": [
{
"typeId": "<type_arg1_type_id>",
"typeArguments": ...
},
{
"typeId": "<type_arg2_type_id>",
"typeArguments": ...
},
...
]
},
...
]
}
Generic Type Parameter
{
"metadataTypeId": <id>,
"type": "generic <name>"
}
<name> is the name of the generic parameter as specified in the struct or enum declaration that uses it.
Some Complex Examples
An Example with Non-Generic Custom Types
Given the following ABI declaration:
#![allow(unused)] fn main() { enum MyEnum { Foo: u64, Bar: bool, } struct MyStruct { bim: u64, bam: MyEnum, } abi MyContract { /// this is a doc comment #[payable, storage(read, write)] fn complex_function( arg1: ([str[5]; 3], bool, b256), arg2: MyStruct, ); } }
its JSON representation would look like:
{
"concreteTypes": [
{
"type": "([str[5]; 3], bool, b256)",
"concreteTypeId": "625531542be70834dd127e771101ac1014111718451bfae996d97abe700c66a5",
"metadataTypeId": 1,
},
{
"type": "str[5]",
"concreteTypeId": "84877f6e98274b9e4721db68b4c0bdb9e52b8e9572c5bd7811c07a41ced882c7",
},
{
"type": "struct MyStruct",
"concreteTypeId": "392d58c694d2d91f3025f2bccfadacf2a105936f5da881b0899185d49f264522",
"metadataTypeId": 4,
},
{
"type": "u64",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
},
{
"type": "b256",
"concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"
},
{
"type": "bool",
"concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903"
},
{
"type": "()",
"concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"
}
],
"metadataTypes": [
{
"metadataTypeId": 1,
"type": "(_, _, _)",
"components": [
{
"name": "__tuple_element",
"typeId": 2,
},
{
"name": "__tuple_element",
"typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903",
},
{
"name": "__tuple_element",
"typeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b",
}
]
},
{
"metadataTypeId": 2,
"type": "[_; 3]",
"components": [
{
"name": "__array_element",
"typeId": "84877f6e98274b9e4721db68b4c0bdb9e52b8e9572c5bd7811c07a41ced882c7",
}
]
},
{
"metadataTypeId": 3,
"type": "enum MyEnum",
"components": [
{
"name": "Foo",
"typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
},
{
"name": "Bar",
"typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903",
}
]
},
{
"metadataTypeId": 4,
"type": "struct MyStruct",
"components": [
{
"name": "bim",
"typeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
},
{
"name": "bam",
"typeId": 3,
}
]
},
],
"functions": [
{
"inputs": [
{
"name": "arg1",
"concreteTypeId": "625531542be70834dd127e771101ac1014111718451bfae996d97abe700c66a5",
},
{
"name": "arg2",
"concreteTypeId": "392d58c694d2d91f3025f2bccfadacf2a105936f5da881b0899185d49f264522"
}
],
"name": "complex_function",
"output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d",
"attributes": [
{
"name": "doc-comment",
"arguments": [" this is a doc comment"]
},
{
"name": "payable",
},
{
"name": "storage",
"arguments": ["read", "write"]
}
]
}
],
"loggedTypes": []
}
An Example with Generic Types
Given the following ABI declaration:
#![allow(unused)] fn main() { enum MyEnum<T, U> { Foo: T, Bar: U, } struct MyStruct<W> { bam: MyEnum<W, W>, } abi MyContract { fn complex_function( arg1: MyStruct<b256>, ); } }
its JSON representation would look like:
{
"concreteTypes": [
{
"type": "struct MyStruct<b256>",
"concreteTypeId": "3ddd5c1768dd7869663dc2f868ea8a8ce68bd6064244dbc4286e2c921c8ce962",
"metadataTypeId": 5,
"typeArguments": [
"7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b"
]
},
{
"type": "b256",
"concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b",
},
{
"type": "()",
"concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d",
}
],
"metadataTypes": [
{
"metadataTypeId": 1,
"type": "enum MyEnum",
"components": [
{
"name": "Foo",
"typeId": 2,
},
{
"name": "Bar",
"typeId": 3,
}
],
"typeParameters": [2, 3]
},
{
"metadataTypeId": 2,
"type": "generic T",
},
{
"metadataTypeId": 3,
"type": "generic U",
},
{
"metadataTypeId": 4,
"type": "generic W",
},
{
"metadataTypeId": 5,
"type": "struct MyStruct",
"components": [
{
"name": "bam",
"typeId": 1,
"typeArguments": [
{
"typeId": 4,
},
{
"typeId": 4,
}
]
}
],
"typeParameters": [4]
}
],
"functions": [
{
"inputs": [
{
"name": "arg1",
"concreteTypeId": "3ddd5c1768dd7869663dc2f868ea8a8ce68bd6064244dbc4286e2c921c8ce962"
}
],
"name": "complex_function",
"output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"
}
],
"loggedTypes": []
}
An Example with Logs
Given the following contract:
#![allow(unused)] fn main() { struct MyStruct<W> { x: W, } abi MyContract { fn logging(); } ... fn logging() { log(MyStruct { x: 42 }); log(MyStruct { x: true }); } }
its JSON representation would look like:
{
"concreteTypes": [
{
"type": "struct MyStruct<bool>",
"concreteTypeId": "eca2a040ce95fc19b7cd5f75bac530d052484d0b1a49267a2eb07a7a1b00c389",
"metadataTypeId": 1,
"typeArguments": [
"b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903"
]
},
{
"type": "struct MyStruct<u64>",
"concreteTypeId": "b2fa346d9ca66ceca61951a27dba2977b2a82b8aa8600670604f286a1393dffe",
"metadataTypeId": 1,
"typeArguments": [
"1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0"
]
},
{
"type": "bool",
"concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903",
},
{
"type": "u64",
"concreteTypeId": "1506e6f44c1d6291cdf46395a8e573276a4fa79e8ace3fc891e092ef32d1b0a0",
},
{
"type": "()",
"concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d",
}
],
"metadataTypes": [
{
"metadataTypeId": 1,
"type": "struct MyStruct",
"components": [
{
"name": "x",
"typeId": 2,
"typeArguments": null
}
],
"typeParameters": [2]
},
{
"metadataTypeId": 2,
"type": "generic W",
},
],
"functions": [
{
"inputs": [],
"name": "logging",
"output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d"
}
],
"loggedTypes": [
{
"logId": "12896678128313068780",
"concreteTypeId": "b2fa346d9ca66ceca61951a27dba2977b2a82b8aa8600670604f286a1393dffe"
},
{
"logId": "16383228984366451899",
"concreteTypeId": "eca2a040ce95fc19b7cd5f75bac530d052484d0b1a49267a2eb07a7a1b00c389"
}
]
}
The logIds are calculated from:
- First 8 bytes of
sha256("struct MyStruct<u64>")=> "12896678128313068780" - First 8 bytes of
sha256("struct MyStruct<bool>")=> "16383228984366451899"
Receipts
Upon execution of ABI calls, i.e scripts being executed, a JSON object representing a list of receipts will be returned to the caller. Below is the JSON specification of the possible receipt types. The root will be receipts_root which will include an array of receipts.
{
"receipts_list":[
{
"type":"<receipt_type>",
...
},
...
]
}
All receipts will have a type property:
type: String; the type of the receipt. Can be one of:
Then, each receipt type will have its own properties. Some of these properties are related to the FuelVM's registers, as specified in more detail here.
Important note: For the JSON representation of receipts, we represent 64-bit unsigned integers as a JSON String due to limitations around the type Number in the JSON specification, which only supports numbers up to 2^{53-1}, while the FuelVM's registers hold values up to 2^64.
Panic Receipt
type:Panic.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.reason: Optional decimal string representation of an 8-bit unsigned integer; panic reason. Not included in canonical receipt form.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.contractId: Optional hexadecimal string representation of the 256-bit (32-byte) contract ID if applicable.nullotherwise. Not included in canonical receipt form. Primary use is for access-list estimation by SDK.
{
"type": "Panic",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"reason": "1",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe",
"contractId": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
Return Receipt
type:Return.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context;nullotherwise.val: Decimal string representation of a 64-bit unsigned integer; value of register$rA.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Return",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"val": "18446744073709551613",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
Call Receipt
type:Call.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context;nullotherwise.to: Hexadecimal representation of the 256-bit (32-byte) contract ID of the callee.amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.gas: Decimal string representation of a 64-bit unsigned integer; amount gas to forward; value in register$rD.param1: Hexadecimal string representation of a 64-bit unsigned integer; first parameter, holds the function selector.param2: Hexadecimal string representation of a 64-bit unsigned integer; second parameter, typically used for the user-specified input to the ABI function being selected.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Call",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
"amount": "10000",
"asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
"gas": "500",
"param1": "0x28f5c28f5c28f5c",
"param2": "0x68db8bac710cb",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
Log Receipt
type:Log.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.ra: Decimal string representation of a 64-bit unsigned integer; value of register$rA.rb: Decimal string representation of a 64-bit unsigned integer; value of register$rB.rc: Decimal string representation of a 64-bit unsigned integer; value of register$rC.rd: Decimal string representation of a 64-bit unsigned integer; value of register$rD.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Log",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"ra": "1844674407370",
"rb": "1844674407371",
"rc": "1844674407372",
"rd": "1844674407373",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
Mint Receipt
type:Mint.sub_id: Hexadecimal string representation of the 256-bit (32-byte) asset sub identifier ID; derived from register$rB.contract_id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context.val: Decimal string representation of a 64-bit unsigned integer; value of register$rA.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Mint",
"sub_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"contract_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"val": "18446744073709551613",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
Burn Receipt
type:Burn.sub_id: Hexadecimal string representation of the 256-bit (32-byte) asset sub identifier ID; derived from register$rB.contract_id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context.val: Decimal string representation of a 64-bit unsigned integer; value of register$rA.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Burn",
"sub_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"contract_id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"val": "18446744073709551613",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
LogData Receipt
type:LogData.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.ra: Decimal string representation of a 64-bit unsigned integer; value of register$rArb: Decimal string representation of a 64-bit unsigned integer; value of register$rBptr: Hexadecimal string representation of a 64-bit unsigned integer; value of register$rC.len: Decimal string representation of a 64-bit unsigned integer; value of register$rD.digest: Hexadecimal string representation of the 256-bit (32-byte) hash ofMEM[$rC, $rD].data: Hexadecimal string representation of the value of the memory rangeMEM[$rC, $rD].pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "LogData",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"ra": "1844674407370",
"rb": "1844674407371",
"ptr": "0x1ad7f29abcc",
"len": "66544",
"digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
"pc": "0xffffffffffffffff",
"data": "0xa7c5ac471b47",
"is": "0xfffffffffffffffe"
}
ReturnData Receipt
type:ReturnData.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.ptr: Hexadecimal string representation of a 64-bit unsigned integer; value of register$rA.len: Decimal string representation of a 64-bit unsigned integer; value of register$rB.digest: Hexadecimal string representation of 256-bit (32-byte), hash ofMEM[$rA, $rB].data: Hexadecimal string representation of the value of the memory rangeMEM[$rA, $rB].pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "ReturnData",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"ptr": "0x1ad7f29abcc",
"len": "1844",
"digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
"pc": "0xffffffffffffffff",
"data": "0xa7c5ac471b47",
"is": "0xfffffffffffffffe"
}
Revert Receipt
type:Revert.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.val: Decimal string representation of a 64-bit unsigned integer; value of register$rA.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Revert",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"val": "1844674407372",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
Transfer Receipt
type:Transfer.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.to: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the recipient contract.amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "Transfer",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
"amount": "10000",
"asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
TransferOut Receipt
type:TransferOut.id: Hexadecimal string representation of the 256-bit (32-byte) contract ID of the current context if in an internal context.nullotherwise.to: Hexadecimal string representation of the 256-bit (32-byte) address to transfer coins to.amount: Decimal string representation of a 64-bit unsigned integer; amount of coins to forward.asset_id: Hexadecimal string representation of the 256-bit (32-byte) asset ID of coins to forward.pc: Hexadecimal string representation of a 64-bit unsigned integer; value of register$pc.is: Hexadecimal string representation of a 64-bit unsigned integer; value of register$is.
{
"type": "TransferOut",
"id": "0x39150017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"to": "0x1c98ff5d121a6d5afc8135821acb3983e460ef0590919266d620bfc7b9b6f24d",
"amount": "10000",
"asset_id": "0xa5149ac6064222922eaa226526b0d853e7871e28c368f6afbcfd60a6ef8d6e61",
"pc": "0xffffffffffffffff",
"is": "0xfffffffffffffffe"
}
ScriptResult Receipt
type:ScriptResult.result: Hexadecimal string representation of a 64-bit unsigned integer;0if script exited successfully,anyotherwise.gas_used: Decimal string representation of a 64-bit unsigned integer; amount of gas consumed by the script.
{
"type": "ScriptResult",
"result": "0x00",
"gas_used": "400"
}
MessageOut Receipt
type:MessageOut.sender: Hexadecimal string representation of the 256-bit (32-byte) address of the message sender:MEM[$fp, 32].recipient: Hexadecimal string representation of the 256-bit (32-byte) address of the message recipient:MEM[$rA, 32].amount: Hexadecimal string representation of a 64-bit unsigned integer; value of register$rD.nonce: Hexadecimal string representation of the 256-bit (32-byte) message nonce as described here.len: Decimal string representation of a 16-bit unsigned integer; value of register$rC.digest: Hexadecimal string representation of 256-bit (32-byte), hash ofMEM[$rB, $rC].data: Hexadecimal string representation of the value of the memory rangeMEM[$rB, $rC].
{
"type": "MessageOut",
"sender": "0x38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff05139150017c9e",
"recipient": "0x4710162c2e3a95a6faff05139150017c9e38e5e280432d546fae345d6ce6d8fe",
"amount": "0xe6d8fe4710162c2e",
"nonce": "0x47101017c9e38e5e280432d546fae345d6ce6d8fe4710162c2e3a95a6faff051",
"len": "65535",
"digest": "0xd28b78894e493c98a196aa51b432b674e4813253257ed9331054ee8d6813b3aa",
"data": "0xa7c5ac471b47"
}
Function Selector Encoding
To select which function you want to call, first, this function must be in an ABI struct of a Sway program. For instance:
#![allow(unused)] fn main() { abi MyContract { fn foo(a: u64); fn bar(a: InputStruct ); } { fn baz(a: ()) { } } }
The function selector is the first 4 bytes of the SHA-256 hash function of the signature of the Sway function being called. Then, these 4 bytes are right-aligned to 8 bytes, left-padded with zeroes.
Note: The word size for the FuelVM is 8 bytes.
Function Signature
The signature is composed of the function name with the parenthesized list of comma-separated parameter types without spaces. All strings encoded with UTF-8. For custom types such as enum and struct, there is a prefix added to the parenthesized list (see below). Generic struct and enum types also accept a list of comma-separated type arguments in between angle brackets right after the prefix.
For instance, to compute the selector for the following function:
#![allow(unused)] fn main() { fn entry_one(arg: u64); }
we should pass "entry_one(u64)" to the sha256() hashing algorithm. The full digest would be:
0x0c36cb9cb766ff60422db243c4fff06d342949da3c64a3c6ac564941f84b6f06
Then we would get only the first 4 bytes of this digest and left-pad it to 8 bytes:
0x000000000c36cb9c
The table below summarizes how each function argument type is encoded
| Type | Encoding |
|---|---|
bool | bool |
u8 | u8 |
u16 | u16 |
u32 | u32 |
u64 | u64 |
b256 | b256 |
struct | s<<arg1>,<arg2>,...>(<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the struct fields and <arg1>, <arg2>, ... are the encoded type arguments |
enum | e<<arg1>>,<arg_2>,...>(<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the enum variants and <arg1>, <arg2>, ... are the encoded type arguments |
str[<n>] | str[<n>] |
array | a[<ty>;<n>] where <ty> is the encoded element type of the array and <n> is its length |
tuple | (<ty1>,<ty2>,...) where <ty1>, <ty2>, ... are the encoded types of the tuple fields |
Note: Non-generic structs and enums do not require angle brackets.
A Complex Example
#![allow(unused)] fn main() { enum MyEnum<V> { Foo: u64, Bar: bool, } struct MyStruct<T, U> { bim: T, bam: MyEnum<u64>, } struct MyOtherStruct { bom: u64, } fn complex_function( arg1: MyStruct<[b256; 3], u8>, arg2: [MyStruct<u64, bool>; 4], arg3: (str[5], bool), arg4: MyOtherStruct, ); }
is encoded as:
abi MyContract {
complex_function(s<a[b256;3],u8>(a[b256;3],e<u64>(u64,bool)),a[s<u64,bool>(u64,e<u64>(u64,bool));4],(str[5],bool),s(u64))
}
which is then hashed into:
51fdfdadc37ff569e281a622281af7ec055f8098c40bc566118cbb48ca5fd28b
and then the encoded function selector is:
0x0000000051fdfdad
Argument Encoding
Version 0
:warning: This version is being deprecated for Version 1 (see below).
When crafting transaction script data, you must encode the arguments you wish to pass to the script.
Types
These are the available types that can be encoded in the ABI:
- Unsigned integers:
u8, 8 bits.u16, 16 bits.u32, 32 bits.u64, 64 bits.u128, 128 bits.u256, 256 bits.
- Boolean:
bool, either0or1encoded identically tou8. - B256:
b256, arbitrary 256-bits value. - Address :
address, a 256-bit (32-byte) address. - Fixed size string
- Array
- Enums (sum types)
- Structs
- Vectors
- Tuples
These types are encoded in-place. Here's how to encode them. We define enc(X) the encoding of the type X.
Unsigned Integers
u<M> where M is either 8, 16, 32, 64, 128 or 256 bits.
enc(X) is the big-endian (i.e. right-aligned) representation of X left-padded with zero-bytes.
- In the case of
Mbeing 8, 16, 32 or 64, total length must be 8 bytes. - If
Mis 128, total length must be 16 bytes. - If
Mis 256, total length must be 32 bytes.
Note: since all integer values are unsigned, there is no need to preserve the sign when extending/padding; padding with only zeroes is sufficient._
Example:
Encoding 42 yields: 0x000000000000002a, which is the hexadecimal representation of the decimal number 42, right-aligned to 8 bytes.
Encoding u128::MAX - 1 yields: 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE, it's right-aligned to 16 bytes
Boolean
enc(X) is 0 if X is false or 1 if X is true, left-padded with zero-bytes. Total length must be 8 bytes. Similar to the u8 encoding.
Example:
Encoding true yields:
0x0000000000000001
B256
b256 is a fixed size bit array of length 256. Used for 256-bit hash digests and other 256-bit types. It is encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745 yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
Address
A 256-bit (32-byte) address, encoded in the same way as a b256 argument: encoded as-is.
Example:
Encoding 0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745 yields:
0xc7fd1d987ada439fc085cfa3c49416cf2b504ac50151e3c2335d60595cb90745
Array
An array of a certain type T, [T; n], where n is the length of the array.
Arrays in Sway have a fixed-length which is known at compile time. This means the ABI encoding for arrays also happens in-place, with no need to account for dynamic sizing.
The encoding for the array contains, in order, the encoding of each element in [T; n], recursively following the encoding for the type T.
For instance, consider the function signature my_func(bool, [u64; 2]) with the values (true, [1, 2]).
The encoding will be:
0x0000000000000001, thetruebool encoded in-place.0x0000000000000001, First element of the parameter[u64; 2],1, encoded as au64.0x0000000000000002, Second element of the parameter[u64; 2],2, encoded as au64.
The resulting encoded ABI will be:
0x000000000000000100000000000000010000000000000002
Fixed-length Strings
Strings have fixed length and are encoded in-place. str[n], where n is the fixed-size of the string. Rather than padding the string, the encoding of the elements of the string is tightly packed. And unlike the other type encodings, the string encoding is left-aligned.
Note that all strings are encoded in UTF-8.
Example:
Encoding "Hello, World" as a str[12] yields:
0x48656c6c6f2c20576f726c6400000000
Note that we're padding with zeroes in order to keep it right-aligned to 8 bytes (FuelVM's word size).
Structs
Encoding ABIs that contain custom types, such as structs, is similar to encoding a set of primitive types. The encoding will proceed in the order that the inner types of a custom type are declared and recursively just like encoding any other type. This way you can encode structs with primitive types or structs with more complex types in them such as other structs, arrays, strings, and enums.
Here's an example:
#![allow(unused)] fn main() { struct InputStruct { field_1: bool, field_2: u8, } abi MyContract { fn foo(a: u64); fn bar(a: InputStruct); } { fn baz(a: ()) { } } }
Calling bar with InputStruct { field_1: true, field_2: 5 } yields:
0x
0000000000000001 // `true` encoded as a bool
0000000000000005 // `5` encoded as u8
A more complex scenario:
#![allow(unused)] fn main() { struct InputStruct { field_1: bool, field_2: [u8; 2], // An array of u8, with length 2. } abi MyContract { fn foo(a: u64); fn bar(a: InputStruct); } { fn baz(a: ()) { } } }
Calling bar with InputStruct { field_1: true, field_2: [1, 2] } yields:
0x
0000000000000001 // `true` encoded as a bool
0000000000000001 // `1` encoded as u8
0000000000000002 // `2` encoded as u8
Enums (sum types)
ABI calls containing enums (sum types) are encoded similarly to structs: encode the discriminant first, then recursively encode the type of the enum variant being passed to the function being called.
#![allow(unused)] fn main() { enum MySumType { X: u32, Y: bool, } abi MyContract { fn foo(a: u64); fn bar(a: MySumType); } { fn baz(a: ()) { } } }
Calling bar with MySumType::X(42) yields:
0x
0000000000000000 // The discriminant of the chosen enum, in this case `0`.
000000000000002a // `42` encoded as u64
If the sum type has variants of different sizes, then left padding is required.
#![allow(unused)] fn main() { enum MySumType { X: b256, Y: u32, } abi MyContract { fn foo(a: u64); fn bar(a: MySumType); } { fn baz(a: ()) { } } }
Calling bar with MySumType::Y(42) yields:
0x
0000000000000001 // The discriminant of the chosen enum, in this case `1`.
0000000000000000 // Left padding
0000000000000000 // Left padding
0000000000000000 // Left padding
000000000000002a // `42` encoded as u64
Note that three words of padding are required because the largest variant of MySumType is 4 words wide.
If all the variants of a sum type are of type (), or unit, then only the discriminant needs to be encoded.
#![allow(unused)] fn main() { enum MySumType { X: (), Y: (), Z: (), } abi MyContract { fn foo(a: u64); fn bar(a: MySumType); } { fn baz(a: ()) { } } }
Calling bar with MySumType::Z yields:
0x
0000000000000002 // The discriminant of the chosen enum, in this case `2`.
Vectors
ABI calls containing vectors are encoded in the following way:
- First, figure out the the length
lof the vector. Its length will also be its capacity. - Encode the content of the vector according to the spec of its type, e.g. for a
Vec<bool>, encode eachboolelement according to theboolspecs. This gives out data that is stored on the heap, and we encode only the pointer to this data
#![allow(unused)] fn main() { abi MyContract { fn foo(a: Vec<u32>); } { fn foo(a: Vec<u32>) {}; } }
Calling foo with vec![1u32, 2u32, 3u32, 4u32]:
lengthis 4,capacityis 4data: [0x0000000000000001, 0x0000000000000002, 0x0000000000000003, 0x0000000000000004], stored at pointer address0x000000000000beef
Note: to understand how the
u32are encoded, see the section about encoding integers.
0x
000000000000beef // pointer address
0000000000000004 // length = 4
0000000000000004 // capacity = 4
At the pointer address, then the vector's content are encoded as such:
0x
0000000000000001 // 1u32
0000000000000002 // 2u32
0000000000000003 // 3u32
0000000000000004 // 4u32
Tuples
ABI calls containing tuples are encoded as such:
If X is a tuple with the type signature (T_1, T_2, ..., T_n), with T_1,...,T_n being any type except for vector, then enc(X) is encoded as the concatenation of enc(T_1), enc(T_2),enc (T_3), ..., enc(T_n).
#![allow(unused)] fn main() { abi MyContract { fn foo(a: (u64, str[3], bool)); } { fn foo(a: (u64, str[4], bool)) {}; } }
Calling foo with (1u64, "fuel", true) :
0x
0000000000000001 // 1u64
6675656c00000000 // "fuel" encoded as per the specs
0000000000000001 // true
Version 1
This version was created to replace the older version 0 described above, and follows three philosophical tenets:
- being self-sufficient: it must be possible to completely decode the original data only using the encoded bytes and the original type (there are no references to data outside the encoded bytes);
- no overhead: only the bare minimum bytes are necessary to do the encoding. No metadata, headers, etc...;
- no relation with runtime memory layout: no padding, no alignment, etc...
Primitive Types
Primitive types will be encoded using the exact number of bits they need:
u8: 1 byte;u16: 2 bytes;u32: 4 bytes;u64: 8 bytes;u256: 32 bytes;b256: 32 bytes;
Arrays
Arrays are encoded without any padding or alignment, with one item after the other.
- [T; 1] is encoded [encode(T)];
- [T; 2] is encoded [encode(T), encode(T)]
Strings
String arrays are encoded just like arrays, without any overhead.
str[1]= 1 bytestr[2]= 2 bytesstr[n]=nbytes
String slices do contain their length as u64, and the string itself is encoded packed without alignment or padding.
"abc"=[0, 0, 0, 0, 0, 0, 0, 3, "a", "b", "c"]
Slices
raw_slice, also being dynamic, contains their length as u64 and is treated as a "slice of bytes". Each byte is encoded as u8 (1 byte) and is packed without alignment and padding.
For example, a slice of three bytes like [0u8, 1u8, 2u8] will be encoded as bytes [0, 0, 0, 0, 0, 0, 0, 3, 0, 1, 2]
Tuple
Tuples are encoded just like arrays, without any overhead like padding and alignment:
(A, B, C)=[encode(A), encode(B), encode(C)]
Structs (v1)
Structs can be encoded in two ways:
- first, with the automatic implementation;
- second, with the custom implementation.
Auto implementation follows the same rules as tuples. So we can imagine that
struct S {
a: A,
b: B,
c: C
}
is encoded the same way as the tuple (A, B, C).
Custom implementation allows the developer to choose how a struct is encoded.
A struct has auto-implemented encoding if no custom was found.
Enums
Enums can also be encoded with the automatic or the custom implementation.
The auto implementation first encoded the variant with a u64 number starting from zero as the first variant and increments this value for each variant, following declaration order.
enum E {
VARIANT_A: A, // <- variant 0
VARIANT_B: B, // <- variant 1
VARIANT_C: C // <- variant 2
}
will be encoded as [encode(variant), encode(value)].
The variant data will be encoded right after the variant tag, without any alignment or padding.
An enum has auto-implemented encoding if no custom was found.
Data Structures
Some common data structures also have well-defined encoding:
Vecwill be encoded as[encode(length), <encode each item>]Byteswill be encoded as[encode(length), <bytes>]Stringwill be encoded as[encode (length), <data>]
All of them first contain the length and then their data right after, without any padding or alignment.
Hash based IDs
Hash based ids are deterministically generated from associated types and are used in the JSON ABI for type IDs and for logId.
This document specifies how the hash based IDS are generated for type IDs and for logId.
Generation
Hash based ids for type IDs are generated from the sha256 of a string that represents the type.
For logIds we use the first 8 bytes of the sha256 of a string that represents the type, this is because the LOG and LOGD opcodes use a 64bit value as log id.
String representation of types
For describing the string representation of type we will use the notation {abi_str(T)} that should be replaced by the respective ABI string representation of the respective type T.
Intrinsics
u8 => "u8"
u16 => "u16"
u32 => "u32"
u64 => "u64"
u256 => "u256"
b256 => "b256"
bool => "bool"
String arrays
String array of size 1 => "str[1]"
String array of size 2 => "str[2]"
etc.
String slices
String slice => "str"
Arrays
[T; 1] => "[{abi_str(T)}; 1]"
[T; 2] => "[{abi_str(T)}; 2]"
etc.
Tuples
() => "()"
(T1) => "({abi_str(T1)})"
(T1,T2) => "({abi_str(T1)}, {abi_str(T2)})"
etc.
Enums
Option enum with type parameter T => "enum std::option::Option<{abi_str(T)}>"
Enum without type parameters named MyEnum => "enum MyEnum"
Enum with type parameter T1 named MyEnum => "enum MyEnum<{abi_str(T1)}>"
Enum with type parameters T1, T2 named MyEnum in my_module => "enum my_module::MyEnum<{abi_str(T1)},{abi_str(T2)}>"
Structs
Vec struct with type parameter T => "struct std::vec::Vec<{abi_str(T)}>"
Struct without type parameters named MyStruct => "struct MyStruct"
Struct with type parameter T1 named MyStruct => "struct MyStruct<{abi_str(T1)}>"
Struct with type parameters T1, T2 named MyStruct in my_module => "struct my_module::MyStruct<{abi_str(T1)},{abi_str(T2)}>"
Generic Type Parameter
Generic type parameter T if root type => "generic T"
Generic type parameter T if not root type => "T" as in "struct MyStruct<T>"
Complex examples composition
Tuple of array and u64 => "([u64,1]; u64)"
Array of Option<u64>=> "[enum std::option::Option<u64>; 3]"
Struct with tuple type parameter => "struct my_module::MyStruct<(u64, u64)>"
Fuel VM Specification
- Introduction
- Parameters
- Semantics
- Flags
- Instruction Set
- VM Initialization
- Contexts
- Predicate Estimation
- Predicate Verification
- Script Execution
- Call Frames
- Ownership
Introduction
This document provides the specification for the Fuel Virtual Machine (FuelVM). The specification covers the types, instruction set, and execution semantics.
Parameters
| name | type | value | note |
|---|---|---|---|
CONTRACT_MAX_SIZE | uint64 | Maximum contract size, in bytes. | |
VM_MAX_RAM | uint64 | 2**26 | 64 MiB. |
MESSAGE_MAX_DATA_SIZE | uint64 | Maximum size of message data, in bytes. |
Semantics
FuelVM instructions are exactly 32 bits (4 bytes) wide and comprise of a combination of:
- Opcode: 8 bits
- Register/special register (see below) identifier: 6 bits
- Immediate value: 12, 18, or 24 bits, depending on operation
Of the 64 registers (6-bit register address space), the first 16 are reserved:
| value | register | name | description |
|---|---|---|---|
0x00 | $zero | zero | Contains zero (0), for convenience. |
0x01 | $one | one | Contains one (1), for convenience. |
0x02 | $of | overflow | Contains overflow/underflow of addition, subtraction, and multiplication. |
0x03 | $pc | program counter | The program counter. Memory address of the current instruction. |
0x04 | $ssp | stack start pointer | Memory address of bottom of current writable stack area. |
0x05 | $sp | stack pointer | Memory address on top of current writable stack area (points to free memory). |
0x06 | $fp | frame pointer | Memory address of beginning of current call frame. |
0x07 | $hp | heap pointer | Memory address below the current bottom of the heap (points to used/OOB memory). |
0x08 | $err | error | Error codes for particular operations. |
0x09 | $ggas | global gas | Remaining gas globally. |
0x0A | $cgas | context gas | Remaining gas in the context. |
0x0B | $bal | balance | Received balance for this context. |
0x0C | $is | instructions start | Pointer to the start of the currently-executing code. |
0x0D | $ret | return value | Return value or pointer. |
0x0E | $retl | return length | Return value length in bytes. |
0x0F | $flag | flags | Flags register. |
Integers are represented in big-endian format, and all operations are unsigned. Boolean false is 0 and Boolean true is 1.
Registers are 64 bits (8 bytes) wide. Words are the same width as registers.
Persistent state (i.e. storage) is a key-value store with 32-byte keys and 32-byte values. Each contract has its own persistent state that is independent of other contracts. This is committed to in a Sparse Binary Merkle Tree.
Flags
| value | name | description |
|---|---|---|
0x01 | F_UNSAFEMATH | If set, undefined arithmetic zeroes target and sets $err without panic. |
0x02 | F_WRAPPING | If set, overflowing arithmetic wraps around and sets $of without panic. |
All other flags are reserved, any must be set to zero.
Instruction Set
A complete instruction set of the Fuel VM is documented in the following page.
VM Initialization
Every time the VM runs, a single monolithic memory of size VM_MAX_RAM bytes is allocated, indexed by individual byte. A stack and heap memory model is used, allowing for dynamic memory allocation in higher-level languages. The stack begins at 0 and grows upward. The heap begins at VM_MAX_RAM and grows downward.
To initialize the VM, the following is pushed on the stack sequentially:
- Transaction hash (
byte[32], word-aligned), computed as defined here. - Base asset ID (
byte[32], word-aligned) MAX_INPUTSpairs of(asset_id: byte[32], balance: uint64), of:- For predicate estimation and predicate verification, zeroes.
- For script execution, the free balance for each asset ID seen in the transaction's inputs, ordered in ascending order. If there are fewer than
MAX_INPUTSasset IDs, the pair has a value of zero.
- Transaction length, in bytes (
uint64, word-aligned). - The transaction, serialized.
Then the following registers are initialized (without explicit initialization, all registers are initialized to zero):
$ssp = 32 + 32 + MAX_INPUTS*(32+8) + size(tx)): the writable stack area starts immediately after the serialized transaction in memory (see above).$sp = $ssp: writable stack area is empty to start.$hp = VM_MAX_RAM: the heap area begins at the top and is empty to start.
Contexts
There are 4 contexts in the FuelVM: predicate estimation, predicate verification, scripts, and calls. A context is an isolated execution environment with defined memory ownership and can be external or internal:
- External: predicate and script.
$fpwill be zero. - Internal: call.
$fpwill be non-zero.
Returning from a context behaves differently depending on whether the context is external or internal.
Predicate Estimation
For any input of type InputType.Coin or InputType.Message, a non-zero predicateLength field means the UTXO being spent is a P2SH rather than a P2PKH output.
For each such input in the transaction, the VM is initialized, then:
$pcand$isare set to the start of the input'spredicatefield.$ggasand$cgasare set toMAX_GAS_PER_PREDICATE.
Predicate estimation will fail if gas is exhausted during execution.
During predicate mode, hitting any of the following instructions causes predicate estimation to halt, returning Boolean false:
- Any contract instruction.
In addition, during predicate mode if $pc is set to a value greater than the end of predicate bytecode (this would allow bytecode outside the actual predicate), predicate estimation halts returning Boolean false.
A predicate that halts without returning Boolean true would not pass verification, making the entire transaction invalid. Note that predicate validity is monotonic with respect to time (i.e. if a predicate evaluates to true then it will always evaluate to true in the future).
After successful execution, predicateGasUsed is set to MAX_GAS_PER_PREDICATE - $ggas.
Predicate Verification
For any input of type InputType.Coin or InputType.Message, a non-zero predicateLength field means the UTXO being spent is a P2SH rather than a P2PKH output.
For each such input in the transaction, the VM is initialized, then:
$pcand$isare set to the start of the input'spredicatefield.$ggasand$cgasare set topredicateGasUsed.
Predicate verification will fail if gas is exhausted during execution.
During predicate mode, hitting any contract instruction causes predicate verification to halt, returning Boolean false.
In addition, during predicate mode if $pc is set to a value greater than the end of predicate bytecode (this would allow bytecode outside the actual predicate), predicate verification halts returning Boolean false.
A predicate that halts without returning Boolean true does not pass verification, making the entire transaction invalid. Note that predicate validity is monotonic with respect to time (i.e. if a predicate evaluates to true then it will always evaluate to true in the future).
After execution, if $ggas is non-zero, predicate verification fails.
Script Execution
If script bytecode is present, transaction validation requires execution.
The VM is initialized, then:
$pcand$isare set to the start of the transaction's script bytecode.$ggasand$cgasare set totx.scriptGasLimit.
Following initialization, execution begins.
For each instruction, its gas cost gc is first computed. If gc > $cgas, deduct $cgas from $ggas and $cgas (i.e. spend all of $cgas and no more), then revert immediately without actually executing the instruction. Otherwise, deduct gc from $ggas and $cgas.
After the script has been executed, tx.receiptsRoot is updated to contain the Merkle root of the receipts, as described in the TransactionScript spec.
Call Frames
Cross-contract calls push a call frame onto the stack, similar to a stack frame used in regular languages for function calls (which may be used by a high-level language that targets the FuelVM). The distinction is as follows:
- Stack frames: store metadata across trusted internal (i.e. intra-contract) function calls. Not supported natively by the FuelVM, but may be used as an abstraction at a higher layer.
- Call frames: store metadata across untrusted external (i.e. inter-contract) calls. Supported natively by the FuelVM.
Call frames are needed to ensure that the called contract cannot mutate the running state of the current executing contract. They segment access rights for memory: the currently-executing contracts may only write to their own call frame and their own heap.
A call frame consists of the following, word-aligned:
| bytes | type | value | description |
|---|---|---|---|
| Unwritable area begins. | |||
| 32 | byte[32] | to | Contract ID for this call. |
| 32 | byte[32] | asset_id | asset ID of forwarded coins. |
| 8*64 | byte[8][64] | regs | Saved registers from previous context. |
| 8 | uint64 | codesize | Code size in bytes, padded to the next word boundary. |
| 8 | byte[8] | param1 | First parameter. |
| 8 | byte[8] | param2 | Second parameter. |
| 1* | byte[] | code | Zero-padded to 8-byte alignment, but individual instructions are not aligned. |
| Unwritable area ends. | |||
| * | Call frame's stack. |
Access rights
Only memory that has been allocated is accessible.
In other words, memory between highest-ever $sp value and current $hp
is inaccessible. Attempting to read or write
memory that has not been allocated will result in VM panic.
Similarly reads or writes that cross from the stack to the heap
will panic. Note that stack remains readable even after stack
frame has been shrunk. However, if the heap is afterwards expanded
to cover that area, the crossing read prohibition still remains,
while all memory is accessible.
Ownership
Whenever memory is written to (i.e. with SB or SW), or write access is granted (i.e. with CALL), ownership must be checked.
If the context is external, the owned memory range is:
[$ssp, $sp): the writable stack area.[$hp, VM_MAX_RAM): the heap area allocated by this script or predicate.
If the context is internal, the owned memory range for a call frame is:
[$ssp, $sp): the writable stack area of the call frame.[$hp, $fp->$hp): the heap area allocated by this call frame.
Executability
Memory is only executable in range [$is, $ssp). Attempting to execute instructions outside these boundaries will cause a panic. This area never overlaps with writable memory, essentially providing W^X protection.
FuelVM Instruction Set
- Reading Guide
- Arithmetic/Logic (ALU) Instructions
ADD: AddADDI: Add immediateAND: ANDANDI: AND immediateDIV: DivideDIVI: Divide immediateEQ: EqualsEXP: ExponentiateEXPI: Exponentiate immediateGT: Greater thanLT: Less thanMLOG: Math logarithmMOD: ModulusMODI: Modulus immediateMOVE: MoveMOVI: Move immediateMROO: Math rootMUL: MultiplyMULI: Multiply immediateMLDV: Fused multiply-divideNOOP: No operationNOT: InvertOR: ORORI: OR immediateSLL: Shift left logicalSLLI: Shift left logical immediateSRL: Shift right logicalSRLI: Shift right logical immediateSUB: SubtractSUBI: Subtract immediateSUBI: Subtract immediateWDCM: 128-bit integer comparisonWQCM: 256-bit integer comparisonWDOP: Misc 128-bit integer operationsWQOP: Misc 256-bit integer operationsWDML: Multiply 128-bit integersWQML: Multiply 256-bit integersWDDV: 128-bit integer divisionWQDV: 256-bit integer divisionWDMD: 128-bit integer fused multiply-divideWQMD: 256-bit integer fused multiply-divideWDAM: Modular 128-bit integer additionWQAM: Modular 256-bit integer additionWDMM: Modular 128-bit integer multiplicationWQMM: Modular 256-bit integer multiplicationXOR: XORXORI: XOR immediate
- Control Flow Instructions
JMP: JumpJI: Jump immediateJNE: Jump if not equalJNEI: Jump if not equal immediateJNZI: Jump if not zero immediateJMPB: Jump relative backwardsJMPF: Jump relative forwardsJNZB: Jump if not zero relative backwardsJNZF: Jump if not zero relative forwardsJNEB: Jump if not equal relative backwardsJNEF: Jump if not equal relative forwardsRET: Return from context
- Memory Instructions
ALOC: Allocate memoryCFE: Extend call frameCFEI: Extend call frame immediateCFS: Shrink call frameCFSI: Shrink call frame immediateLB: Load byteLW: Load wordMCL: Memory clearMCLI: Memory clear immediateMCP: Memory copyMCPI: Memory copy immediateMEQ: Memory equalityPOPH: Pop a set of high registers from stackPOPL: Pop a set of low registers from stackPSHH: Push a set of high registers to stackPSHL: Push a set of low registers to stackSB: Store byteSW: Store word
- Contract Instructions
BAL: Balance of contract IDBHEI: Block heightBHSH: Block hashBURN: Burn existing coinsCALL: Call contractCB: Coinbase contract idCCP: Code copyCROO: Code Merkle rootCSIZ: Code sizeLDC: Load code from an external contract, blob or memoryLOG: Log eventLOGD: Log data eventMINT: Mint new coinsRETD: Return from context with dataRVRT: RevertSMO: Send message to outputSCWQ: State clear sequential 32 byte slotsSRW: State read wordSRWQ: State read sequential 32 byte slotsSWW: State write wordSWWQ: State write sequential 32 byte slotsTIME: Timestamp at heightTR: Transfer coins to contractTRO: Transfer coins to output
- Blob Instructions
- Cryptographic Instructions
- Other Instructions
Reading Guide
This page provides a description of all instructions for the FuelVM. Encoding is read as a sequence of one 8-bit value (the opcode identifier) followed by four 6-bit values (the register identifiers or immediate value). A single i indicates a 6-bit immediate value, i i indicates a 12-bit immediate value, i i i indicates an 18-bit immediate value, and i i i i indicates a 24-bit immediate value. All immediate values are interpreted as big-endian unsigned integers. If the instruction would be shorter than the full 32 bits, the remaining part is reserved and must be zero.
- The syntax
MEM[x, y]used in this page means the memory range starting at bytex, of lengthybytes. - The syntax
STATE[x, y]used in this page means the sequence of storage slots starting at keyxand spanningybytes.
Panics
Some instructions may panic, i.e. enter an unrecoverable state. Additionally, attempting to execute an instruction not in this list causes a panic and consumes no gas. Instructions with reserved part having a non-zero value will likewise panic. How a panic is handled depends on context:
- In a predicate context, cease VM execution and return
false. - In other contexts, revert (described below).
On a non-predicate panic, append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Panic |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
then append an additional receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 1 |
gas_used | uint64 | Gas consumed by the script. |
Receipts
The number of receipts is limited to 216, with the last two reserved to panic and script result receipts. Trying to add any other receipts after 216-2 will panic.
Effects
A few instructions are annotated with the effects they produce, the table below explains each effect:
| effect name | description |
|---|---|
| Storage read | Instruction reads from storage slots |
| Storage write | Instruction writes to storage slots |
| External call | External contract call instruction |
| Balance tree read | Instruction reads from the balance tree |
| Balance tree write | Instruction writes to the balance tree |
| Output message | Instruction sends a message to a recipient address |
If an instruction is not annotated with an effect, it means it does not produce any of the aforementioned affects.
Arithmetic/Logic (ALU) Instructions
All these instructions advance the program counter $pc by 4 after performing their operation.
Normally, if the result of an ALU operation is mathematically undefined (e.g. dividing by zero),
the VM panics. However, if the F_UNSAFEMATH flag is set, $err is set to true
and execution continues.
If an operation would overflow, so that the result doesn't fit into the target field, the VM will panic.
Results below zero are also considered overflows. If the F_WRAPPING flag is set,
instead $of is set to true or the overflowing part of the result, depending on the operation.
ADD: Add
| Description | Adds two registers. |
| Operation | $rA = $rB + $rC; |
| Syntax | add $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of is assigned the overflow of the operation.
$err is cleared.
ADDI: Add immediate
| Description | Adds a register and an immediate value. |
| Operation | $rA = $rB + imm; |
| Syntax | addi $rA, $rB, immediate |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
$of is assigned the overflow of the operation.
$err is cleared.
AND: AND
| Description | Bitwise ANDs two registers. |
| Operation | $rA = $rB & $rC; |
| Syntax | and $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
ANDI: AND immediate
| Description | Bitwise ANDs a register and an immediate value. |
| Operation | $rA = $rB & imm; |
| Syntax | andi $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
imm is extended to 64 bits, with the high 52 bits set to 0.
$of and $err are cleared.
DIV: Divide
| Description | Divides two registers. |
| Operation | $rA = $rB // $rC; |
| Syntax | div $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
If $rC == 0, $rA is cleared and $err is set to true.
Otherwise, $err is cleared.
$of is cleared.
DIVI: Divide immediate
| Description | Divides a register and an immediate value. |
| Operation | $rA = $rB // imm; |
| Syntax | divi $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
If imm == 0, $rA is cleared and $err is set to true.
Otherwise, $err is cleared.
$of is cleared.
EQ: Equals
| Description | Compares two registers for equality. |
| Operation | $rA = $rB == $rC; |
| Syntax | eq $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
EXP: Exponentiate
| Description | Raises one register to the power of another. |
| Operation | $rA = $rB ** $rC; |
| Syntax | exp $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
If the result cannot fit in 8 bytes, $of is set to 1 and $rA is instead set to 0, otherwise $of is cleared.
$err is cleared.
EXPI: Exponentiate immediate
| Description | Raises one register to the power of an immediate value. |
| Operation | $rA = $rB ** imm; |
| Syntax | expi $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
If the result cannot fit in 8 bytes, $of is set to 1 and $rA is instead set to 0, otherwise $of is cleared.
$err is cleared.
GT: Greater than
| Description | Compares two registers for greater-than. |
| Operation | $rA = $rB > $rC; |
| Syntax | gt $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
LT: Less than
| Description | Compares two registers for less-than. |
| Operation | $rA = $rB < $rC; |
| Syntax | lt $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
MLOG: Math logarithm
| Description | The (integer) logarithm base $rC of $rB. |
| Operation | $rA = math.floor(math.log($rB, $rC)); |
| Syntax | mlog $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
If $rB == 0, both $rA and $of are cleared and $err is set to true.
If $rC <= 1, both $rA and $of are cleared and $err is set to true.
Otherwise, $of and $err are cleared.
MOD: Modulus
| Description | Modulo remainder of two registers. |
| Operation | $rA = $rB % $rC; |
| Syntax | mod $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
If $rC == 0, both $rA and $of are cleared and $err is set to true.
Otherwise, $of and $err are cleared.
MODI: Modulus immediate
| Description | Modulo remainder of a register and an immediate value. |
| Operation | $rA = $rB % imm; |
| Syntax | modi $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
If imm == 0, both $rA and $of are cleared and $err is set to true.
Otherwise, $of and $err are cleared.
MOVE: Move
| Description | Copy from one register to another. |
| Operation | $rA = $rB; |
| Syntax | move $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
MOVI: Move immediate
| Description | Copy an immediate value into a register. |
| Operation | $rA = imm; |
| Syntax | movi $rA, imm |
| Encoding | 0x00 rA i i i |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
MROO: Math root
| Description | The (integer) $rCth root of $rB. |
| Operation | $rA = math.floor(math.root($rB, $rC)); |
| Syntax | mroo $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
If $rC == 0, both $rA and $of are cleared and $err is set to true.
Otherwise, $of and $err are cleared.
MUL: Multiply
| Description | Multiplies two registers. |
| Operation | $rA = $rB * $rC; |
| Syntax | mul $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of is assigned the overflow of the operation.
$err is cleared.
MULI: Multiply immediate
| Description | Multiplies a register and an immediate value. |
| Operation | $rA = $rB * imm; |
| Syntax | mul $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
$of is assigned the overflow of the operation.
$err is cleared.
MLDV: Fused multiply-divide
| Description | Multiplies two registers with arbitrary precision, then divides by a third register. |
| Operation | a = (b * c) / d; |
| Syntax | mldv $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | Division by zero is treated as division by 1 << 64 instead. |
If the divisor ($rD) is zero, then instead the value is divided by 1 << 64. This returns the higher half of the 128-bit multiplication result. This operation never overflows.
If the result of after the division doesn't fit into a register, $of is assigned the overflow of the operation. Otherwise, $of is cleared.
$err is cleared.
NOOP: No operation
| Description | Performs no operation. |
| Operation | |
| Syntax | noop |
| Encoding | 0x00 - - - - |
| Notes |
$of and $err are cleared.
NOT: Invert
| Description | Bitwise NOT a register. |
| Operation | $rA = ~$rB; |
| Syntax | not $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
OR: OR
| Description | Bitwise ORs two registers. |
| Operation | $rA = $rB | $rC; |
| Syntax | or $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
ORI: OR immediate
| Description | Bitwise ORs a register and an immediate value. |
| Operation | $rA = $rB | imm; |
| Syntax | ori $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
imm is extended to 64 bits, with the high 52 bits set to 0.
$of and $err are cleared.
SLL: Shift left logical
| Description | Left shifts a register by a register. |
| Operation | $rA = $rB << $rC; |
| Syntax | sll $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes | Zeroes are shifted in. |
Panic if:
$rAis a reserved register
$of and $err are cleared.
SLLI: Shift left logical immediate
| Description | Left shifts a register by an immediate value. |
| Operation | $rA = $rB << imm; |
| Syntax | slli $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes | Zeroes are shifted in. |
Panic if:
$rAis a reserved register
$of and $err are cleared.
SRL: Shift right logical
| Description | Right shifts a register by a register. |
| Operation | $rA = $rB >> $rC; |
| Syntax | srl $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes | Zeroes are shifted in. |
Panic if:
$rAis a reserved register
$of and $err are cleared.
SRLI: Shift right logical immediate
| Description | Right shifts a register by an immediate value. |
| Operation | $rA = $rB >> imm; |
| Syntax | srli $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes | Zeroes are shifted in. |
Panic if:
$rAis a reserved register
$of and $err are cleared.
SUB: Subtract
| Description | Subtracts two registers. |
| Operation | $rA = $rB - $rC; |
| Syntax | sub $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes | $of is assigned the overflow of the operation. |
Panic if:
$rAis a reserved register
$of is assigned the underflow of the operation, as though $of is the high byte of a 128-bit register.
$err is cleared.
SUBI: Subtract immediate
| Description | Subtracts a register and an immediate value. |
| Operation | $rA = $rB - imm; |
| Syntax | subi $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes | $of is assigned the overflow of the operation. |
Panic if:
$rAis a reserved register
$of is assigned the underflow of the operation, as though $of is the high byte of a 128-bit register.
$err is cleared.
WDCM: 128-bit integer comparison
| Description | Compare or examine two 128-bit integers using selected mode |
| Operation | b = mem[$rB,16];c = indirect?mem[$rC,16]:$rC;$rA = cmp_op(b,c); |
| Syntax | wdcm $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The six-bit immediate value is used to select operating mode, as follows:
| Bits | Short name | Description |
|---|---|---|
...XXX | mode | Compare mode selection |
.XX... | reserved | Reserved and must be zero |
X..... | indirect | Is rhs operand ($rC) indirect or not |
Then the actual operation that's performed:
mode | Name | Description |
|---|---|---|
| 0 | eq | Equality (==) |
| 1 | ne | Inequality (!=) |
| 2 | lt | Less than (<) |
| 3 | gt | Greater than (>) |
| 4 | lte | Less than or equals (<=) |
| 5 | gte | Greater than or equals (>=) |
| 6 | lzc | Leading zero count the lhs argument (lzcnt). Discards rhs. |
| 7 | - | Reserved and must not be used |
The leading zero count can be used to compute rounded-down log2 of a number using the following formula TOTAL_BITS - 1 - lzc(n). Note that log2(0) is undefined, and will lead to integer overflow with this method.
Clears $of and $err.
Panic if:
- A reserved compare mode is given
$rAis a reserved register$rB + 16overflows or> VM_MAX_RAMindirect == 1and$rC + 16overflows or> VM_MAX_RAM
WQCM: 256-bit integer comparison
| Description | Compare or examine two 256-bit integers using selected mode |
| Operation | b = mem[$rB,32];c = indirect?mem[$rC,32]:$rC;$rA = cmp_op(b,c); |
| Syntax | wqcm $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The immediate value is interpreted identically to WDCM.
Clears $of and $err.
Panic if:
- A reserved compare mode is given
$rAis a reserved register$rB + 32overflows or> VM_MAX_RAMindirect == 1and$rC + 32overflows or> VM_MAX_RAM
WDOP: Misc 128-bit integer operations
| Description | Perform an ALU operation on two 128-bit integers |
| Operation | b = mem[$rB,16];c = indirect?mem[$rC,16]:$rC;mem[$rA,16] = op(b,c); |
| Syntax | wdop $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The six-bit immediate value is used to select operating mode, as follows:
| Bits | Short name | Description |
|---|---|---|
...XXX | op | Operation selection, see below |
.XX... | reserved | Reserved and must be zero |
X..... | indirect | Is rhs operand ($rC) indirect or not |
Then the actual operation that's performed:
op | Name | Description |
|---|---|---|
| 0 | add | Add |
| 1 | sub | Subtract |
| 2 | not | Invert bits (discards rhs) |
| 3 | or | Bitwise or |
| 4 | xor | Bitwise exclusive or |
| 5 | and | Bitwise and |
| 6 | shl | Shift left (logical) |
| 7 | shr | Shift right (logical) |
Operations behave $of and $err similarly to their 64-bit counterparts, except that $of is set to 1 instead of the overflowing part.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 16]does not pass ownership check $rB + 16overflows or> VM_MAX_RAMindirect == 1and$rC + 16overflows or> VM_MAX_RAM
WQOP: Misc 256-bit integer operations
| Description | Perform an ALU operation on two 256-bit integers |
| Operation | b = mem[$rB,32];c = indirect?mem[$rC,32]:$rC;mem[$rA,32] = op(b,c); |
| Syntax | wqop $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The immediate value is interpreted identically to WDOP.
Operations behave $of and $err similarly to their 64-bit counterparts.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 32]does not pass ownership check $rB + 32overflows or> VM_MAX_RAMindirect == 1and$rC + 32overflows or> VM_MAX_RAM
WDML: Multiply 128-bit integers
| Description | Perform integer multiplication operation on two 128-bit integers. |
| Operation | b=indirect0?mem[$rB,16]:$rB;c=indirect1?mem[$rC,16]:$rC;mem[$rA,16]=b*c; |
| Syntax | wdml $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The six-bit immediate value is used to select operating mode, as follows:
| Bits | Short name | Description |
|---|---|---|
..XXXX | reserved | Reserved and must be zero |
.X.... | indirect0 | Is lhs operand ($rB) indirect or not |
X..... | indirect1 | Is rhs operand ($rC) indirect or not |
$of is set to 1 in case of overflow, and cleared otherwise.
$err is cleared.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 16]does not pass ownership check indirect0 == 1and$rB + 16overflows or> VM_MAX_RAMindirect1 == 1and$rC + 16overflows or> VM_MAX_RAM
WQML: Multiply 256-bit integers
| Description | Perform integer multiplication operation on two 256-bit integers. |
| Operation | b=indirect0?mem[$rB,32]:$rB;c=indirect1?mem[$rC,32]:$rC;mem[$rA,32]=b*c; |
| Syntax | wqml $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The immediate value is interpreted identically to WDML.
$of is set to 1 in case of overflow, and cleared otherwise.
$err is cleared.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 32]does not pass ownership check indirect0 == 1and$rB + 32overflows or> VM_MAX_RAMindirect1 == 1and$rC + 32overflows or> VM_MAX_RAM
WDDV: 128-bit integer division
| Description | Divide a 128-bit integer by another. |
| Operation | b = mem[$rB,16];c = indirect?mem[$rC,16]:$rC;mem[$rA,16] = b / c; |
| Syntax | wddv $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The six-bit immediate value is used to select operating mode, as follows:
| Bits | Short name | Description |
|---|---|---|
.XXXXX | reserved | Reserved and must be zero |
X..... | indirect | Is rhs operand ($rC) indirect or not |
$of is cleared.
If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 16]does not pass ownership check $rB + 16overflows or> VM_MAX_RAMindirect == 1and$rC + 16overflows or> VM_MAX_RAM
WQDV: 256-bit integer division
| Description | Divide a 256-bit integer by another. |
| Operation | b = mem[$rB,32];c = indirect?mem[$rC,32]:$rC;mem[$rA,32] = b / c; |
| Syntax | wqdv $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
The immediate value is interpreted identically to WDDV.
$of is cleared.
If the rhs operand is zero, MEM[$rA, 32] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- Reserved bits of the immediate are set
- The memory range
MEM[$rA, 32]does not pass ownership check $rB + 32overflows or> VM_MAX_RAMindirect == 1and$rC + 32overflows or> VM_MAX_RAM
WDMD: 128-bit integer fused multiply-divide
| Description | Combined multiply-divide of 128-bit integers with arbitrary precision. |
| Operation | b=mem[$rB,16];c=mem[$rC,16];d=mem[$rD,16];mem[$rA,16]=(b * c) / d; |
| Syntax | wdmd $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | Division by zero is treated as division by 1 << 128 instead. |
If the divisor MEM[$rA, 16] is zero, then instead the value is divided by 1 << 128. This returns the higher half of the 256-bit multiplication result.
If the result of after the division is larger than operand size, $of is set to one. Otherwise, $of is cleared.
$err is cleared.
Panic if:
- The memory range
MEM[$rA, 16]does not pass ownership check $rB + 16overflows or> VM_MAX_RAM$rC + 16overflows or> VM_MAX_RAM$rD + 16overflows or> VM_MAX_RAM
WQMD: 256-bit integer fused multiply-divide
| Description | Combined multiply-divide of 256-bit integers with arbitrary precision. |
| Operation | b=mem[$rB,32];c=mem[$rC,32];d=mem[$rD,32];mem[$rA,32]=(b * c) / d; |
| Syntax | wqmd $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | Division by zero is treated as division by 1 << 256 instead. |
If the divisor MEM[$rA, 32] is zero, then instead the value is divided by 1 << 256. This returns the higher half of the 512-bit multiplication result.
If the result of after the division is larger than operand size, $of is set to one. Otherwise, $of is cleared.
$err is cleared.
Panic if:
- The memory range
MEM[$rA, 32]does not pass ownership check $rB + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM$rD + 32overflows or> VM_MAX_RAM
WDAM: Modular 128-bit integer addition
| Description | Add two 128-bit integers and compute modulo remainder with arbitrary precision. |
| Operation | b=mem[$rB,16];c=mem[$rC,16];d=mem[$rD,16];mem[$rA,16] = (b+c)%d; |
| Syntax | wdam $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
$of is cleared.
If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- The memory range
MEM[$rA, 16]does not pass ownership check $rB + 16overflows or> VM_MAX_RAM$rC + 16overflows or> VM_MAX_RAM$rD + 16overflows or> VM_MAX_RAM
WQAM: Modular 256-bit integer addition
| Description | Add two 256-bit integers and compute modulo remainder with arbitrary precision. |
| Operation | b=mem[$rB,32];c=mem[$rC,32];d=mem[$rD,32];mem[$rA,32] = (b+c)%d; |
| Syntax | wqam $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
$of is cleared.
If the rhs operand is zero, MEM[$rA, 32] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- The memory range
MEM[$rA, 32]does not pass ownership check $rB + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM$rD + 32overflows or> VM_MAX_RAM
WDMM: Modular 128-bit integer multiplication
| Description | Multiply two 128-bit integers and compute modulo remainder with arbitrary precision. |
| Operation | b=mem[$rB,16];c=mem[$rC,16];d=mem[$rD,16];mem[$rA,16] = (b*c)%d; |
| Syntax | wdmm $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
$of is cleared.
If the rhs operand is zero, MEM[$rA, 16] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- The memory range
MEM[$rA, 16]does not pass ownership check $rB + 16overflows or> VM_MAX_RAM$rC + 16overflows or> VM_MAX_RAM$rD + 16overflows or> VM_MAX_RAM
WQMM: Modular 256-bit integer multiplication
| Description | Multiply two 256-bit integers and compute modulo remainder with arbitrary precision. |
| Operation | b=mem[$rB,32];c=mem[$rC,32];d=mem[$rD,32];mem[$rA,32] = (b*c)%d; |
| Syntax | wqmm $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
$of is cleared.
If the rhs operand is zero, MEM[$rA, 32] is cleared and $err is set to true. Otherwise, $err is cleared.
Panic if:
- The memory range
MEM[$rA, 32]does not pass ownership check $rB + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM$rD + 32overflows or> VM_MAX_RAM
XOR: XOR
| Description | Bitwise XORs two registers. |
| Operation | $rA = $rB ^ $rC; |
| Syntax | xor $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
XORI: XOR immediate
| Description | Bitwise XORs a register and an immediate value. |
| Operation | $rA = $rB ^ imm; |
| Syntax | xori $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register
$of and $err are cleared.
Control Flow Instructions
JMP: Jump
| Description | Jumps to the code instruction offset by a register. |
| Operation | $pc = $is + $rA * 4; |
| Syntax | jmp $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Panic if:
$is + $rA * 4 > VM_MAX_RAM - 1
JI: Jump immediate
| Description | Jumps to the code instruction offset by imm. |
| Operation | $pc = $is + imm * 4; |
| Syntax | ji imm |
| Encoding | 0x00 i i i i |
| Notes |
Panic if:
$is + imm * 4 > VM_MAX_RAM - 1
JNE: Jump if not equal
| Description | Jump to the code instruction offset by a register if $rA is not equal to $rB. |
| Operation | if $rA != $rB:$pc = $is + $rC * 4;else:$pc += 4; |
| Syntax | jne $rA $rB $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$is + $rC * 4 > VM_MAX_RAM - 1and the jump would be performed (i.e.$rA != $rB)
JNEI: Jump if not equal immediate
| Description | Jump to the code instruction offset by imm if $rA is not equal to $rB. |
| Operation | if $rA != $rB:$pc = $is + imm * 4;else:$pc += 4; |
| Syntax | jnei $rA $rB imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$is + imm * 4 > VM_MAX_RAM - 1and the jump would be performed (i.e.$rA != $rB)
JNZI: Jump if not zero immediate
| Description | Jump to the code instruction offset by imm if $rA is not equal to $zero. |
| Operation | if $rA != $zero:$pc = $is + imm * 4;else:$pc += 4; |
| Syntax | jnzi $rA imm |
| Encoding | 0x00 rA i i i |
| Notes |
Panic if:
$is + imm * 4 > VM_MAX_RAM - 1and the jump would be performed (i.e.$rA != $zero)
JMPB: Jump relative backwards
| Description | Jump $rA + imm instructions backwards. |
| Operation | $pc -= ($rA + imm + 1) * 4; |
| Syntax | jmpb $rA imm |
| Encoding | 0x00 rA i i i |
| Notes |
Panic if:
$pc - ($rA + imm + 1) * 4 < 0
JMPF: Jump relative forwards
| Description | Jump $rA + imm instructions forwards |
| Operation | $pc += ($rA + imm + 1) * 4; |
| Syntax | jmpf $rA imm |
| Encoding | 0x00 rA i i i |
| Notes |
Panic if:
$pc + ($rA + imm + 1) * 4 > VM_MAX_RAM - 1
JNZB: Jump if not zero relative backwards
| Description | Jump $rB + imm instructions backwards if $rA != $zero. |
| Operation | if $rA != $zero:$pc -= ($rB + imm + 1) * 4;else:$pc += 4; |
| Syntax | jnzb $rA $rB imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$pc - ($rB + imm + 1) * 4 < 0
JNZF: Jump if not zero relative forwards
| Description | Jump $rB + imm instructions forwards if $rA != $zero. |
| Operation | if $rA != $zero:$pc += ($rB + imm + 1) * 4;else:$pc += 4; |
| Syntax | jnzf $rA $rB imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$pc + ($rB + imm + 1) * 4 > VM_MAX_RAM - 1
JNEB: Jump if not equal relative backwards
| Description | Jump $rC + imm instructions backwards if $rA != $rB. |
| Operation | if $rA != $rB:$pc -= ($rC + imm + 1) * 4;else:$pc += 4; |
| Syntax | jneb $rA $rB $rC imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
Panic if:
$pc - ($rC + imm + 1) * 4 < 0
JNEF: Jump if not equal relative forwards
| Description | Jump $rC + imm instructions forwards if $rA != $rB. |
| Operation | if $rA != $rB:$pc += ($rC + imm + 1) * 4;else:$pc += 4; |
| Syntax | jnef $rA $rB $rC imm |
| Encoding | 0x00 rA rB rC i |
| Notes |
Panic if:
$pc + ($rC + imm + 1) * 4 > VM_MAX_RAM - 1
RET: Return from context
| Description | Returns from context with value $rA. |
| Operation | return($rA); |
| Syntax | ret $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Return |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val | uint64 | Value of register $rA. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
If current context is external, append an additional receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 0 |
gas_used | uint64 | Gas consumed by the script. |
If current context is external, cease VM execution and return $rA.
Returns from contract call, popping the call frame. Before popping perform the following operations.
Return the unused forwarded gas to the caller:
$cgas = $cgas + $fp->$cgas(add remaining context gas from previous context to current remaining context gas)
Set the return value:
$ret = $rA$retl = 0
Then pop the call frame and restore all registers except $ggas, $cgas, $ret, $retl and $hp. Afterwards, set the following registers:
$pc = $pc + 4(advance program counter from where we called)
Memory Instructions
All these instructions advance the program counter $pc by 4 after performing their operation.
ALOC: Allocate memory
| Description | Allocate a number of bytes from the heap. |
| Operation | $hp = $hp - $rA; |
| Syntax | aloc $rA |
| Encoding | 0x00 rA - - - |
| Notes | Newly allocated memory is zeroed. |
Panic if:
$hp - $rAunderflows$hp - $rA < $sp
CFE: Extend call frame
| Description | Extend the current call frame's stack. |
| Operation | $sp = $sp + $rA |
| Syntax | cfe $rA |
| Encoding | 0x00 rA - - - |
| Notes | Does not initialize memory. |
Panic if:
$sp + $rAoverflows$sp + $rA > $hp
CFEI: Extend call frame immediate
| Description | Extend the current call frame's stack by an immediate value. |
| Operation | $sp = $sp + imm |
| Syntax | cfei imm |
| Encoding | 0x00 i i i i |
| Notes | Does not initialize memory. |
Panic if:
$sp + immoverflows$sp + imm > $hp
CFS: Shrink call frame
| Description | Shrink the current call frame's stack. |
| Operation | $sp = $sp - $rA |
| Syntax | cfs $rA |
| Encoding | 0x00 $rA - - - |
| Notes | Does not clear memory. |
Panic if:
$sp - $rAunderflows$sp - $rA < $ssp
CFSI: Shrink call frame immediate
| Description | Shrink the current call frame's stack by an immediate value. |
| Operation | $sp = $sp - imm |
| Syntax | cfsi imm |
| Encoding | 0x00 i i i i |
| Notes | Does not clear memory. |
Panic if:
$sp - immunderflows$sp - imm < $ssp
LB: Load byte
| Description | A byte is loaded from the specified address offset by imm. |
| Operation | $rA = MEM[$rB + imm, 1]; |
| Syntax | lb $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register$rB + imm + 1overflows or> VM_MAX_RAM
LW: Load word
| Description | A word is loaded from the specified address offset by imm. |
| Operation | $rA = MEM[$rB + (imm * 8), 8]; |
| Syntax | lw $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rAis a reserved register$rB + (imm * 8) + 8overflows or> VM_MAX_RAM
MCL: Memory clear
| Description | Clear bytes in memory. |
| Operation | MEM[$rA, $rB] = 0; |
| Syntax | mcl $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rA + $rBoverflows or> VM_MAX_RAM- The memory range
MEM[$rA, $rB]does not pass ownership check
MCLI: Memory clear immediate
| Description | Clear bytes in memory. |
| Operation | MEM[$rA, imm] = 0; |
| Syntax | mcli $rA, imm |
| Encoding | 0x00 rA i i i |
| Notes |
Panic if:
$rA + immoverflows or> VM_MAX_RAM- The memory range
MEM[$rA, imm]does not pass ownership check
MCP: Memory copy
| Description | Copy bytes in memory. |
| Operation | MEM[$rA, $rC] = MEM[$rB, $rC]; |
| Syntax | mcp $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rA + $rCoverflows or> VM_MAX_RAM$rB + $rCoverflows or> VM_MAX_RAM- The memory ranges
MEM[$rA, $rC]andMEM[$rB, $rC]overlap - The memory range
MEM[$rA, $rC]does not pass ownership check
MCPI: Memory copy immediate
| Description | Copy bytes in memory. |
| Operation | MEM[$rA, imm] = MEM[$rB, imm]; |
| Syntax | mcpi $rA, $rB, imm |
| Encoding | 0x00 rA rB imm imm |
| Notes |
Panic if:
$rA + immoverflows or> VM_MAX_RAM$rB + immoverflows or> VM_MAX_RAM- The memory ranges
MEM[$rA, imm]andMEM[$rB, imm]overlap - The memory range
MEM[$rA, imm]does not pass ownership check
MEQ: Memory equality
| Description | Compare bytes in memory. |
| Operation | $rA = MEM[$rB, $rD] == MEM[$rC, $rD]; |
| Syntax | meq $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
Panic if:
$rAis a reserved register$rB + $rDoverflows or> VM_MAX_RAM$rC + $rDoverflows or> VM_MAX_RAM
PSHH: Push a set of high registers to stack
| Description | Push a set of registers from range 40..64 to the stack in order. |
| Operation | tmp=$sp;$sp+=popcnt(imm)*8;MEM[tmp,$sp]=registers[40..64].mask(imm) |
| Syntax | pshh imm |
| Encoding | 0x00 i i i i |
| Notes | The immediate value is used as a bitmask for selecting the registers. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pushes the register 61 followed by register 62.
Panic if:
$sp + popcnt(imm)*8overflows$sp + popcnt(imm)*8 > $hp
PSHL: Push a set of low registers to stack
| Description | Push a set of registers from range 16..40 to the stack in order. |
| Operation | tmp=$sp;$sp+=popcnt(imm)*8;MEM[tmp,$sp]=registers[16..40].mask(imm) |
| Syntax | pshl imm |
| Encoding | 0x00 i i i i |
| Notes | The immediate value is used as a bitmask for selecting the registers. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pushes the register 37 followed by register 38.
Panic if:
$sp + popcnt(imm)*8overflows$sp + popcnt(imm)*8 > $hp
POPH: Pop a set of high registers from stack
| Description | Pop to a set of registers from range 40..64 from the stack. |
| Operation | tmp=$sp-popcnt(imm)*8;registers[40..64].mask(imm)=MEM[tmp,$sp]$sp-=tmp; |
| Syntax | poph imm |
| Encoding | 0x00 i i i i |
| Notes | The immediate value is used as a bitmask for selecting the registers. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pops the register 62 followed by register 61.
Note that the order is reverse from PSHH, so that PSHH a; POPH a returns to the original state.
Panic if:
$sp - popcnt(imm)*8overflows$sp - popcnt(imm)*8 < $ssp
POPL: Pop a set of low registers from stack
| Description | Pop to a set of registers from range 16..40 from the stack. |
| Operation | tmp=$sp-popcnt(imm)*8;registers[16..40].mask(imm)=MEM[tmp,$sp]$sp-=tmp; |
| Syntax | poph imm |
| Encoding | 0x00 i i i i |
| Notes | The immediate value is used as a bitmask for selecting the registers. |
The nth bit of the bitmask corresponds to nth entry of the register range. In other words, the most significant (i.e. leftmost) bit of the bitmask corresponds to the highest register index. So for instance bitmask 011000000000000000000000 pops the register 38 followed by register 37.
Note that the order is reverse from PSHL, so that PSHL a; POPL a returns to the original state.
Panic if:
$sp - popcnt(imm)*8overflows$sp - popcnt(imm)*8 < $ssp
SB: Store byte
| Description | The least significant byte of $rB is stored at the address $rA offset by imm. |
| Operation | MEM[$rA + imm, 1] = $rB[7, 1]; |
| Syntax | sb $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rA + imm + 1overflows or> VM_MAX_RAM- The memory range
MEM[$rA + imm, 1]does not pass ownership check
SW: Store word
| Description | The value of $rB is stored at the address $rA offset by imm. |
| Operation | MEM[$rA + (imm * 8), 8] = $rB; |
| Syntax | sw $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Panic if:
$rA + (imm * 8) + 8overflows or> VM_MAX_RAM- The memory range
MEM[$rA + (imm * 8), 8]does not pass ownership check
Contract Instructions
All these instructions advance the program counter $pc by 4 after performing their operation, except for CALL, RETD and RVRT.
BAL: Balance of contract ID
| Description | Set $rA to the balance of asset ID at $rB for contract with ID at $rC. |
| Operation | $rA = balance(MEM[$rB, 32], MEM[$rC, 32]); |
| Syntax | bal $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Effects | Balance tree read |
| Notes |
Where helper balance(asset_id: byte[32], contract_id: byte[32]) -> uint64 returns the current balance of asset_id of contract with ID contract_id.
Panic if:
$rAis a reserved register$rB + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM- Contract with ID
MEM[$rC, 32]is not intx.inputs
BHEI: Block height
| Description | Get Fuel block height. |
| Operation | $rA = blockheight(); |
| Syntax | bhei $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Panic if:
$rAis a reserved register
BHSH: Block hash
| Description | Get block header hash. |
| Operation | MEM[$rA, 32] = blockhash($rB); |
| Syntax | bhsh $rA $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, 32]does not pass ownership check
Block header hashes for blocks with height greater than or equal to current block height are zero (0x00**32).
BURN: Burn existing coins
| Description | Burn $rA coins of the $rB ID from the current contract. |
| Operation | burn($rA, $rB); |
| Syntax | burn $rA $rB |
| Encoding | 0x00 rA rB - - |
| Notes | $rB is a pointer to a 32 byte ID in memory. |
The asset ID is constructed using the asset ID construction method.
Panic if:
$rB + 32overflows or> VM_MAX_RAM- Balance of asset ID from
constructAssetID(MEM[$fp, 32], MEM[$rB, 32])of output with contract IDMEM[$fp, 32]minus$rAunderflows $fp == 0(in the script context)
For output with contract ID MEM[$fp, 32], decrease balance of asset ID constructAssetID(MEM[$fp, 32], MEM[$rB, 32]) by $rA.
This modifies the balanceRoot field of the appropriate output.
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Burn |
sub_id | byte[32] | Asset sub identifier MEM[$rB, $rB + 32]. |
contract_id | byte[32] | Contract ID of the current context. |
val | uint64 | Value of register $rA. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
CALL: Call contract
| Description | Call contract. |
| Operation | |
| Syntax | call $rA $rB $rC $rD |
| Encoding | 0x00 rA rB rC rD |
| Effects | External call |
| Notes |
There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM- Contract with ID
MEM[$rA, 32]is not intx.inputs - Reading past
MEM[VM_MAX_RAM - 1] - In an external context, if
$rB > MEM[balanceOfStart(MEM[$rC, 32]), 8] - In an internal context, if
$rBis greater than the balance of asset IDMEM[$rC, 32]of output with contract IDMEM[$fp, 32]
Register $rA is a memory address from which the following fields are set (word-aligned):
| bytes | type | value | description |
|---|---|---|---|
| 32 | byte[32] | to | Contract ID to call. |
| 8 | byte[8] | param1 | First parameter. |
| 8 | byte[8] | param2 | Second parameter. |
$rB is the amount of coins to forward. $rC points to the 32-byte asset ID of the coins to forward. $rD is the amount of gas to forward. If it is set to an amount greater than the available gas, all available gas is forwarded.
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Call |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Contract ID of called contract. |
amount | uint64 | Amount of coins to forward, i.e. $rB. |
asset_id | byte[32] | Asset ID of coins to forward, i.e. MEM[$rC, 32]. |
gas | uint64 | Gas to forward, i.e. min($rD, $cgas). |
param1 | uint64 | First parameter. |
param2 | uint64 | Second parameter. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
For output with contract ID MEM[$rA, 32], increase balance of asset ID MEM[$rC, 32] by $rB. In an external context, decrease MEM[balanceOfStart(MEM[$rC, 32]), 8] by $rB. In an internal context, decrease asset ID MEM[$rC, 32] balance of output with contract ID MEM[$fp, 32] by $rB.
A call frame is pushed at $sp. In addition to filling in the values of the call frame, the following registers are set:
$fp = $sp(on top of the previous call frame is the beginning of this call frame)- Set
$sspand$spto the start of the writable stack area of the call frame. - Set
$pcand$isto the starting address of the code. $flagset to zero.$bal = $rB(forward coins)$cgas = $rDor all available gas (forward gas)
This modifies the balanceRoot field of the appropriate output(s).
CB: Coinbase contract id
| Description | Get the coinbase contract id associated with the block proposer. |
| Operation | MEM[$rA, 32] = coinbase(); |
| Syntax | cb $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, 32]does not pass ownership check
CCP: Code copy
| Description | Copy $rD bytes of code starting at $rC for contract with ID equal to the 32 bytes in memory starting at $rB into memory starting at $rA. |
| Operation | MEM[$rA, $rD] = code($rB, $rC, $rD); |
| Syntax | ccp $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | If $rD is greater than the code size, zero bytes are filled in. |
This is used only for reading and inspecting code of other contracts.
Use LDC to load code for executing.
Panic if:
$rA + $rDoverflows or> VM_MAX_RAM$rB + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, $rD]does not pass ownership check - Contract with ID
MEM[$rB, 32]is not intx.inputs
CROO: Code Merkle root
| Description | Set the 32 bytes in memory starting at $rA to the code root for contract with ID equal to the 32 bytes in memory starting at $rB. |
| Operation | MEM[$rA, 32] = coderoot(MEM[$rB, 32]); |
| Syntax | croo $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rB + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, 32]does not pass ownership check - Contract with ID
MEM[$rB, 32]is not intx.inputs
Code root computation is defined here.
CSIZ: Code size
| Description | Set $rA to the size of the code for contract with ID equal to the 32 bytes in memory starting at $rB. |
| Operation | $rA = codesize(MEM[$rB, 32]); |
| Syntax | csiz $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rAis a reserved register$rB + 32overflows or> VM_MAX_RAM- Contract with ID
MEM[$rB, 32]is not intx.inputs
LDC: Load code from an external contract, blob or memory
| Description | Copy $rC bytes of code at offset $rB from object identified with $rA into memory starting at $ssp. Object type is in imm. |
| Operation | code = match imm { 0 => contract_code(mem[$rA,32]), 1 => blob_payload(mem[$rA,32]), 2 => mem[$ra, ..] }; MEM[$ssp, $rC] = code[$rB, $rC]; |
| Syntax | ldc $rA, $rB, $rC, imm |
| Encoding | 0x00 rA rB rC imm |
| Notes | If $rC is greater than the code size, zero bytes are filled in. Final length is always padded to word boundary. |
Object type from imm determines the source for loading as follows:
imm | Object type |
|---|---|
0 | Contract code |
1 | Blob payload |
2 | VM memory |
| other | reserved |
Panic if:
$ssp + $rCoverflows or> VM_MAX_RAMimm <= 1and$rA + 32overflows or> VM_MAX_RAM$ssp + $rC >= $hpimm == 0and$rC > CONTRACT_MAX_SIZEimm == 0and contract with IDMEM[$rA, 32]is not intx.inputsimm == 0and context is a predicateimm == 1and blob with IDMEM[$rA, 32]is not found in the chain stateimm == 2and$rA + $rB + $rCoverflows or> VM_MAX_RAMimm >= 3(reserved value)
Increment $fp->codesize, $ssp by $rC padded to word alignment. Then set $sp to $ssp.
This instruction can be used to concatenate the code of multiple contracts or blobs together. It can only be used when the stack area of the call frame is zero-sized.
LOG: Log event
| Description | Log an event. This is a no-op. |
| Operation | log($rA, $rB, $rC, $rD); |
| Syntax | log $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Log |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val0 | uint64 | Value of register $rA. |
val1 | uint64 | Value of register $rB. |
val2 | uint64 | Value of register $rC. |
val3 | uint64 | Value of register $rD. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
LOGD: Log data event
| Description | Log an event. This is a no-op. |
| Operation | logd($rA, $rB, $rC, $rD); |
| Syntax | logd $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes |
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.LogData |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val0 | uint64 | Value of register $rA. |
val1 | uint64 | Value of register $rB. |
ptr | uint64 | Value of register $rC. |
len | uint64 | Value of register $rD. |
digest | byte[32] | Hash of MEM[$rC, $rD]. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
Logs the memory range MEM[$rC, $rD].
Panics if:
$rC + $rDoverflows or> VM_MAX_RAM
MINT: Mint new coins
| Description | Mint $rA coins of the $rB ID from the current contract. |
| Operation | mint($rA, $rB); |
| Syntax | mint $rA $rB |
| Encoding | 0x00 rA rB - - |
| Notes | $rB is a pointer to a 32 byte ID in memory |
The asset ID will be constructed using the asset ID construction method.
Panic if:
$rB + 32overflows or> VM_MAX_RAM- Balance of asset ID
constructAssetID(MEM[$fp, 32], MEM[$rB])of output with contract IDMEM[$fp, 32]plus$rAoverflows $fp == 0(in the script context)
For output with contract ID MEM[$fp, 32], increase balance of asset ID constructAssetID(MEM[$fp, 32], MEM[$rB]) by $rA.
This modifies the balanceRoot field of the appropriate output.
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Mint |
sub_id | byte[32] | Asset sub identifier MEM[$rB, $rB + 32]. |
contract_id | byte[32] | Contract ID of the current context. |
val | uint64 | Value of register $rA. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
RETD: Return from context with data
| Description | Returns from context with value MEM[$rA, $rB]. |
| Operation | returndata($rA, $rB); |
| Syntax | retd $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rA + $rBoverflows or> VM_MAX_RAM
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.ReturnData |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
ptr | uint64 | Value of register $rA. |
len | uint64 | Value of register $rB. |
digest | byte[32] | Hash of MEM[$rA, $rB]. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
If current context is a script, append an additional receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 0 |
gas_used | uint64 | Gas consumed by the script. |
If current context is external, cease VM execution and return MEM[$rA, $rB].
Returns from contract call, popping the call frame. Before popping, perform the following operations.
Return the unused forwarded gas to the caller:
$cgas = $cgas + $fp->$cgas(add remaining context gas from previous context to current remaining context gas)
Set the return value:
$ret = $rA$retl = $rB
Then pop the call frame and restore all registers except $ggas, $cgas, $ret, $retl and $hp. Afterwards, set the following registers:
$pc = $pc + 4(advance program counter from where we called)
RVRT: Revert
| Description | Halt execution, reverting state changes and returning value in $rA. |
| Operation | revert($rA); |
| Syntax | rvrt $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Revert |
id | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
val | uint64 | Value of register $rA. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
Then append an additional receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.ScriptResult |
result | uint64 | 1 |
gas_used | uint64 | Gas consumed by the script. |
Cease VM execution and revert script effects. After a revert:
- All
OutputContractoutputs will have the samebalanceRootandstateRootas on initialization. - All
OutputVariableoutputs will haveto,amount, andasset_idof zero.
SMO: Send message out
| Description | Send a message to recipient address MEM[$rA, 32] from the MEM[$fp, 32] sender with message data MEM[$rB, $rC] and the $rD amount of base asset coins. |
| Operation | outputmessage(MEM[$fp, 32], MEM[$rA, 32], MEM[$rB, $rC], $rD); |
| Syntax | smo $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Effects | Output message |
| Notes |
There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rB + $rCoverflows or> VM_MAX_RAM$rC > MESSAGE_MAX_DATA_SIZE- In an external context, if
$rD > MEM[balanceOfStart(0), 8] - In an internal context, if
$rDis greater than the balance of asset ID 0 of output with contract IDMEM[$fp, 32]
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.MessageOut |
sender | byte[32] | The address of the message sender: MEM[$fp, 32]. |
recipient | byte[32] | The address of the message recipient: MEM[$rA, 32]. |
amount | uint64 | Amount of base asset coins sent with message: $rD. |
nonce | byte[32] | The message nonce as described here. |
len | uint64 | Length of message data, in bytes: $rC. |
digest | byte[32] | Hash of MEM[$rB, $rC]. |
In an external context, decrease MEM[balanceOfStart(0), 8] by $rD. In an internal context, decrease asset ID 0 balance of output with contract ID MEM[$fp, 32] by $rD. This modifies the balanceRoot field of the appropriate contract that had its' funds deducted.
SCWQ: State clear sequential 32 byte slots
| Description | A sequential series of 32 bytes is cleared from the current contract's state. |
| Operation | STATE[MEM[$rA, 32], 32 * $rC] = None; |
| Syntax | scwq $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rBis a reserved register$fp == 0(in the script context)
Register $rB will be set to false if any storage slot in the requested range was already unset (default) and true if all the slots were set.
SRW: State read word
| Description | A word is read from the current contract's state. |
| Operation | $rA = STATE[MEM[$rC, 32]][0, 8]; |
| Syntax | srw $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Effects | Storage read |
| Notes | Returns zero if the state element does not exist. |
Panic if:
$rAis a reserved register$rBis a reserved register$rC + 32overflows or> VM_MAX_RAM$fp == 0(in the script context)
Register $rB will be set to false if the requested slot is unset (default) and true if it's set.
SRWQ: State read sequential 32 byte slots
| Description | A sequential series of 32 bytes is read from the current contract's state. |
| Operation | MEM[$rA, 32 * rD] = STATE[MEM[$rC, 32], 32 * rD]; |
| Syntax | srwq $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Effects | Storage read |
| Notes | Returns zero if the state element does not exist. |
Panic if:
$rA + 32 * rDoverflows or> VM_MAX_RAM$rC + 32 * rDoverflows or> VM_MAX_RAM$rBis a reserved register- The memory range
MEM[$rA, 32 * rD]does not pass ownership check $fp == 0(in the script context)
Register $rB will be set to false if any storage slot in the requested range is unset (default) and true if all the slots are set.
SWW: State write word
| Description | A word is written to the current contract's state. |
| Operation | STATE[MEM[$rA, 32]][0, 8] = $rC;STATE[MEM[$rA, 32]][8, 24] = 0; |
| Syntax | sww $rA $rB $rC |
| Encoding | 0x00 rA rB rC - |
| Effects | Storage write |
| Notes | Additional gas is charged when a new storage slot is created. |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rBis a reserved register$fp == 0(in the script context)
The last 24 bytes of STATE[MEM[$rA, 32]] are set to 0. Register $rB will be set to the number of new slots written, i.e. 1 if the slot was previously unset, and 0 if it already contained a value.
SWWQ: State write sequential 32 byte slots
| Description | A sequential series of 32 bytes is written to the current contract's state. |
| Operation | STATE[MEM[$rA, 32], 32 * $rD] = MEM[$rC, 32 * $rD]; |
| Syntax | swwq $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Effects | Storage write |
| Notes | Additional gas is charged when for each new storage slot created. |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rC + 32 * $rDoverflows or> VM_MAX_RAM$rBis a reserved register$fp == 0(in the script context)
Register $rB will be set to the number of storage slots that were previously unset, and were set by this operation.
TIME: Timestamp at height
| Description | Get timestamp of block at given height. |
| Operation | $rA = time($rB); |
| Syntax | time $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rAis a reserved register$rBis greater than the current block height.
Gets the timestamp of the block at height $rB. Time is in TAI64 format.
TR: Transfer coins to contract
| Description | Transfer $rB coins with asset ID at $rC to contract with ID at $rA. |
| Operation | transfer(MEM[$rA, 32], $rB, MEM[$rC, 32]); |
| Syntax | tr $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Effects | Balance tree read, balance tree write |
| Notes |
There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM- Contract with ID
MEM[$rA, 32]is not intx.inputs - In an external context, if
$rB > MEM[balanceOfStart(MEM[$rC, 32]), 8] - In an internal context, if
$rBis greater than the balance of asset IDMEM[$rC, 32]of output with contract IDMEM[$fp, 32] $rB == 0
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.Transfer |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Contract ID of contract to transfer coins to. |
amount | uint64 | Amount of coins transferred. |
asset_id | byte[32] | asset ID of coins transferred. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
For output with contract ID MEM[$rA, 32], increase balance of asset ID MEM[$rC, 32] by $rB. In an external context, decrease MEM[balanceOfStart(MEM[$rC, 32]), 8] by $rB. In an internal context, decrease asset ID MEM[$rC, 32] balance of output with contract ID MEM[$fp, 32] by $rB.
This modifies the balanceRoot field of the appropriate output(s).
TRO: Transfer coins to output
| Description | Transfer $rC coins with asset ID at $rD to address at $rA, with output $rB. |
| Operation | transferout(MEM[$rA, 32], $rB, $rC, MEM[$rD, 32]); |
| Syntax | tro $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Effects | Balance tree read, balance tree write |
| Notes |
There is a balanceOfStart(asset_id: byte[32]) -> uint32 helper that returns the memory address of the remaining free balance of asset_id. If asset_id has no free balance remaining, the helper panics.
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rD + 32overflows or> VM_MAX_RAM$rB > tx.outputsCount- In an external context, if
$rC > MEM[balanceOfStart(MEM[$rD, 32]), 8] - In an internal context, if
$rCis greater than the balance of asset IDMEM[$rD, 32]of output with contract IDMEM[$fp, 32] $rC == 0tx.outputs[$rB].type != OutputType.Variabletx.outputs[$rB].amount != 0
Append a receipt to the list of receipts:
| name | type | description |
|---|---|---|
type | ReceiptType | ReceiptType.TransferOut |
from | byte[32] | Contract ID of current context if in an internal context, zero otherwise. |
to | byte[32] | Address to transfer coins to. |
amount | uint64 | Amount of coins transferred. |
asset_id | byte[32] | asset ID of coins transferred. |
pc | uint64 | Value of register $pc. |
is | uint64 | Value of register $is. |
In an external context, decrease MEM[balanceOfStart(MEM[$rD, 32]), 8] by $rC. In an internal context, decrease asset ID MEM[$rD, 32] balance of output with contract ID MEM[$fp, 32] by $rC. Then set:
tx.outputs[$rB].to = MEM[$rA, 32]tx.outputs[$rB].amount = $rCtx.outputs[$rB].asset_id = MEM[$rD, 32]
This modifies the balanceRoot field of the appropriate output(s).
Blob Instructions
All these instructions advance the program counter $pc by 4 after performing their operation.
BSIZ: Blob size
| Description | Set $rA to the size of the blob with ID equal to the 32 bytes in memory starting at $rB. |
| Operation | $rA = len(blob(MEM[$rB, 32])); |
| Syntax | bsiz $rA, $rB |
| Encoding | 0x00 rA rB - - |
| Notes |
Panic if:
$rAis a reserved register$rB + 32overflows or> VM_MAX_RAM- Blob ID
MEM[$rB, 32]is not found
BLDD: Load data from a blob
|-------------|-------------------------------------------------------------------------------------------------------------|
| Description | Load 32-byte blob id at $rB, and copy $rD bytes starting from $rC into $sA. |
| Operation | MEM[$rA, $rD] = blob(MEM[$rB, 32])[$rC, $rD]; |
| Syntax | bldd $rA, $rB, rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | If $rC > blob size, zero bytes are filled in. |
Panic if:
$rA + $rDoverflows or> VM_MAX_RAMor> $hp$rB + 32overflows or> VM_MAX_RAM- Blob ID
MEM[$rB, 32]is not found
Cryptographic Instructions
All these instructions advance the program counter $pc by 4 after performing their operation.
ECK1: Secp256k1 signature recovery
| Description | The 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC. |
| Operation | MEM[$rA, 64] = ecrecover_k1(MEM[$rB, 64], MEM[$rC, 32]); |
| Syntax | eck1 $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes | Takes message hash as an input. You can use S256 to hash the message if needed. |
Panic if:
$rA + 64overflows or> VM_MAX_RAM$rB + 64overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, 64]does not pass ownership check
Signatures and signature verification are specified here.
If the signature cannot be verified, MEM[$rA, 64] is set to 0 and $err is set to 1, otherwise $err is cleared.
To get the address from the public key, hash the public key with SHA-2-256.
ECR1: Secp256r1 signature recovery
| Description | The 64-byte public key (x, y) recovered from 64-byte signature starting at $rB on 32-byte message hash starting at $rC. |
| Operation | MEM[$rA, 64] = ecrecover_r1(MEM[$rB, 64], MEM[$rC, 32]); |
| Syntax | ecr1 $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes | Takes message hash as an input. You can use S256 to hash the message if needed. |
Panic if:
$rA + 64overflows or> VM_MAX_RAM$rB + 64overflows or> VM_MAX_RAM$rC + 32overflows or> VM_MAX_RAM- The memory range
MEM[$rA, 64]does not pass ownership check
Signatures and signature verification are specified here.
If the signature cannot be verified, MEM[$rA, 64] is set to 0 and $err is set to 1, otherwise $err is cleared.
To get the address from the public key, hash the public key with SHA-2-256.
ED19: EdDSA curve25519 verification
| Description | Verification 64-byte signature at $rB with 32-byte public key at $rA for a message starting at $rC with length $rD. |
| Operation | ed19verify(MEM[$rA, 32], MEM[$rB, 64], MEM[$rC, $rD]); |
| Syntax | ed19 $rA, $rB, $rC, $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | Takes message instead of hash. For backwards compatibility reasons, if $rD == 0, it will be treated as 32. |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rB + 64overflows or> VM_MAX_RAM$rC + $rDoverflows or> VM_MAX_RAM
Verification are specified here.
If there is an error in verification, $err is set to 1, otherwise $err is cleared.
K256: keccak-256
| Description | The keccak-256 hash of $rC bytes starting at $rB. |
| Operation | MEM[$rA, 32] = keccak256(MEM[$rB, $rC]); |
| Syntax | k256 $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rB + $rCoverflows or> VM_MAX_RAM- The memory range
MEM[$rA, 32]does not pass ownership check
S256: SHA-2-256
| Description | The SHA-2-256 hash of $rC bytes starting at $rB. |
| Operation | MEM[$rA, 32] = sha256(MEM[$rB, $rC]); |
| Syntax | s256 $rA, $rB, $rC |
| Encoding | 0x00 rA rB rC - |
| Notes |
Panic if:
$rA + 32overflows or> VM_MAX_RAM$rB + $rCoverflows or> VM_MAX_RAM- The memory range
MEM[$rA, 32]does not pass ownership check
Other Instructions
All these instructions advance the program counter $pc by 4 after performing their operation.
ECAL: Call external function
| Description | Call an external function that has full access to the VM state. |
| Operation | external(&mut vm, $rA, $rB, $rC, $rD) |
| Syntax | ecal $rA $rB $rC $rD |
| Encoding | 0x00 rA rB rC rD |
| Notes | Does nothing by default, but the VM user can define this to do anything. |
This function provides an escape hatch from the VM, similar to ecall instruction of RISC-V. The suggested convention is to use $rA for "system call number", i.e. identifying the procedure to call, but all arguments can be used freely. The operation can modify the VM state freely, including writing to registers and memory. Again, the suggested convention is to use $rA for the return value and $err for any possible errors. However, these conventions can be ignored when necessary.
Panic if:
- The external function panics.
FLAG: Set flags
| Description | Set $flag to $rA. |
| Operation | $flag = $rA; |
| Syntax | flag $rA |
| Encoding | 0x00 rA - - - |
| Notes |
Panic if:
- Any reserved flags are set
GM: Get metadata
| Description | Get metadata from memory. |
| Operation | Varies (see below). |
| Syntax | gm $rA, imm |
| Encoding | 0x00 rA imm imm imm |
| Notes |
Read metadata from memory. A convenience instruction to avoid manually extracting metadata.
| name | value | description |
|---|---|---|
GM_IS_CALLER_EXTERNAL | 0x00001 | Get if caller is external. |
GM_GET_CALLER | 0x00002 | Get caller's contract ID. |
GM_GET_VERIFYING_PREDICATE | 0x00003 | Get index of current predicate. |
GM_GET_CHAIN_ID | 0x00004 | Get the value of CHAIN_ID |
GM_TX_START | 0x00005 | Transaction start memory address |
GM_BASE_ASSET_ID | 0x00006 | Base asset ID |
If imm == GM_IS_CALLER_EXTERNAL:
Panic if:
$fp == 0(in an external context)
Set $rA to true if parent is an external context, false otherwise.
If imm == GM_GET_CALLER:
Panic if:
$fp == 0(in an external context)$fp->$fp == 0(if parent context is external)
Set $rA to $fp->$fp (i.e. $rA will point to the previous call frame's contract ID).
If imm == GM_GET_VERIFYING_PREDICATE:
Panic if:
- not in a predicate context
Set $rA to the index of the currently-verifying predicate.
GTF: Get transaction fields
| Description | Get transaction fields. |
| Operation | Varies (see below). |
| Syntax | gtf $rA, $rB, imm |
| Encoding | 0x00 rA rB i i |
| Notes |
Get fields from the transaction.
| name | imm | set $rA to |
|---|---|---|
GTF_TYPE | 0x001 | tx.type |
GTF_SCRIPT_GAS_LIMIT | 0x002 | tx.scriptGasLimit |
GTF_SCRIPT_SCRIPT_LENGTH | 0x003 | tx.scriptLength |
GTF_SCRIPT_SCRIPT_DATA_LENGTH | 0x004 | tx.scriptDataLength |
GTF_SCRIPT_INPUTS_COUNT | 0x005 | tx.inputsCount |
GTF_SCRIPT_OUTPUTS_COUNT | 0x006 | tx.outputsCount |
GTF_SCRIPT_WITNESSES_COUNT | 0x007 | tx.witnessesCount |
GTF_SCRIPT_SCRIPT | 0x009 | Memory address of tx.script |
GTF_SCRIPT_SCRIPT_DATA | 0x00A | Memory address of tx.scriptData |
GTF_SCRIPT_INPUT_AT_INDEX | 0x00B | Memory address of tx.inputs[$rB] |
GTF_SCRIPT_OUTPUT_AT_INDEX | 0x00C | Memory address of t.outputs[$rB] |
GTF_SCRIPT_WITNESS_AT_INDEX | 0x00D | Memory address of tx.witnesses[$rB] |
GTF_TX_LENGTH | 0x00E | Length of raw transaction types in memory |
GTF_CREATE_BYTECODE_WITNESS_INDEX | 0x101 | tx.bytecodeWitnessIndex |
GTF_CREATE_STORAGE_SLOTS_COUNT | 0x102 | tx.storageSlotsCount |
GTF_CREATE_INPUTS_COUNT | 0x103 | tx.inputsCount |
GTF_CREATE_OUTPUTS_COUNT | 0x104 | tx.outputsCount |
GTF_CREATE_WITNESSES_COUNT | 0x105 | tx.witnessesCount |
GTF_CREATE_SALT | 0x106 | Memory address of tx.salt |
GTF_CREATE_STORAGE_SLOT_AT_INDEX | 0x107 | Memory address of tx.storageSlots[$rB] |
GTF_CREATE_INPUT_AT_INDEX | 0x108 | Memory address of tx.inputs[$rB] |
GTF_CREATE_OUTPUT_AT_INDEX | 0x109 | Memory address of t.outputs[$rB] |
GTF_CREATE_WITNESS_AT_INDEX | 0x10A | Memory address of tx.witnesses[$rB] |
GTF_INPUT_TYPE | 0x200 | tx.inputs[$rB].type |
GTF_INPUT_COIN_TX_ID | 0x201 | Memory address of tx.inputs[$rB].txID |
GTF_INPUT_COIN_OUTPUT_INDEX | 0x202 | tx.inputs[$rB].outputIndex |
GTF_INPUT_COIN_OWNER | 0x203 | Memory address of tx.inputs[$rB].owner |
GTF_INPUT_COIN_AMOUNT | 0x204 | tx.inputs[$rB].amount |
GTF_INPUT_COIN_ASSET_ID | 0x205 | Memory address of tx.inputs[$rB].asset_id |
GTF_INPUT_COIN_WITNESS_INDEX | 0x207 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_COIN_PREDICATE_LENGTH | 0x209 | tx.inputs[$rB].predicateLength |
GTF_INPUT_COIN_PREDICATE_DATA_LENGTH | 0x20A | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_COIN_PREDICATE | 0x20B | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_COIN_PREDICATE_DATA | 0x20C | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_COIN_PREDICATE_GAS_USED | 0x20D | tx.inputs[$rB].predicateGasUsed |
GTF_INPUT_CONTRACT_CONTRACT_ID | 0x225 | Memory address of tx.inputs[$rB].contractID |
GTF_INPUT_MESSAGE_SENDER | 0x240 | Memory address of tx.inputs[$rB].sender |
GTF_INPUT_MESSAGE_RECIPIENT | 0x241 | Memory address of tx.inputs[$rB].recipient |
GTF_INPUT_MESSAGE_AMOUNT | 0x242 | tx.inputs[$rB].amount |
GTF_INPUT_MESSAGE_NONCE | 0x243 | Memory address of tx.inputs[$rB].nonce |
GTF_INPUT_MESSAGE_WITNESS_INDEX | 0x244 | tx.inputs[$rB].witnessIndex |
GTF_INPUT_MESSAGE_DATA_LENGTH | 0x245 | tx.inputs[$rB].dataLength |
GTF_INPUT_MESSAGE_PREDICATE_LENGTH | 0x246 | tx.inputs[$rB].predicateLength |
GTF_INPUT_MESSAGE_PREDICATE_DATA_LENGTH | 0x247 | tx.inputs[$rB].predicateDataLength |
GTF_INPUT_MESSAGE_DATA | 0x248 | Memory address of tx.inputs[$rB].data |
GTF_INPUT_MESSAGE_PREDICATE | 0x249 | Memory address of tx.inputs[$rB].predicate |
GTF_INPUT_MESSAGE_PREDICATE_DATA | 0x24A | Memory address of tx.inputs[$rB].predicateData |
GTF_INPUT_MESSAGE_PREDICATE_GAS_USED | 0x24B | tx.inputs[$rB].predicateGasUsed |
GTF_OUTPUT_TYPE | 0x300 | tx.outputs[$rB].type |
GTF_OUTPUT_COIN_TO | 0x301 | Memory address of tx.outputs[$rB].to |
GTF_OUTPUT_COIN_AMOUNT | 0x302 | tx.outputs[$rB].amount |
GTF_OUTPUT_COIN_ASSET_ID | 0x303 | Memory address of tx.outputs[$rB].asset_id |
GTF_OUTPUT_CONTRACT_INPUT_INDEX | 0x304 | tx.outputs[$rB].inputIndex |
GTF_OUTPUT_CONTRACT_BALANCE_ROOT | 0x305 | Memory address of tx.outputs[$rB].balanceRoot |
GTF_OUTPUT_CONTRACT_STATE_ROOT | 0x306 | Memory address of tx.outputs[$rB].stateRoot |
GTF_OUTPUT_CONTRACT_CREATED_CONTRACT_ID | 0x307 | Memory address of tx.outputs[$rB].contractID |
GTF_OUTPUT_CONTRACT_CREATED_STATE_ROOT | 0x308 | Memory address of tx.outputs[$rB].stateRoot |
GTF_WITNESS_DATA_LENGTH | 0x400 | tx.witnesses[$rB].dataLength |
GTF_WITNESS_DATA | 0x401 | Memory address of tx.witnesses[$rB].data |
GTF_POLICY_TYPES | 0x500 | tx.policies.policyTypes |
GTF_POLICY_TIP | 0x501 | tx.policies[0x00].tip |
GTF_POLICY_WITNESS_LIMIT | 0x502 | tx.policies[count_ones(0b11 & tx.policyTypes) - 1].witnessLimit |
GTF_POLICY_MATURITY | 0x503 | tx.policies[count_ones(0b111 & tx.policyTypes) - 1].maturity |
GTF_POLICY_MAX_FEE | 0x504 | tx.policies[count_ones(0b1111 & tx.policyTypes) - 1].maxFee |
Panic if:
$rAis a reserved registerimmis not one of the values listed above- The value of
$rBresults in an out of bounds access for variable-length fields - The input or output type does not match (
OutputChangeandOutputVariablecount asOutputCoin) - The requested policy type is not set for this transaction.
For fixed-length fields, the value of $rB is ignored.
Networks
Specifications for network-specific components of the protocol.
PoA Network
Consensus Header
Wraps the application header.
| name | type | description |
|---|---|---|
prevRoot | byte[32] | Merkle root of all previous consensus header hashes (i.e. not including this block). |
height | uint32 | Height of this block. |
timestamp | uint64 | Time this block was created, in TAI64 format. |
applicationHash | byte[32] | Hash of serialized application header for this block. |
Consensus for the consensus header is a single signature from the authority over the hash of the serialized consensus header.
Since the system is secure under the assumption the authority is honest, there is no need for committing to the authority signatures in the header.
Testing
Test suites for verifying the correctness of a Fuel implementation.
Sparse Merkle Tree Test Specifications
Version
0.1.1
Last updated 2022/07/11
Abstract
This document outlines a test suite specification that can be used to verify the correctness of a Sparse Merkle Tree's outputs. The scope of this document covers only Sparse Merkle Tree (SMT) implementations that are compliant with Celestia Sparse Merkle Tree Specification. The goal of this document is to equip SMT library developers with a supplemental indicator of correctness. Libraries implementing an SMT can additionally implement this test suite specification in the code base's native language. Passing all tests in the concrete test suite is an indication of correctness and consistency with the reference specification; however, it is not an absolute guarantee.
The tests described in this document are designed to test features common to most Sparse Merkle Tree implementations. Test specifications are agnostic of the implementation details or language, and therefore take a black-box testing approach. A test specification may provide an example of what a compliant test may look like in the form of pseudocode.
A test specification follows the format:
- Test name
- Test description
- Test inputs
- Test outputs
- Example pseudocode
For a concrete test to comply with its corresponding test specification, the System Under Test (SUT) must take in the prescribed inputs. When the SUT produces the prescribed outputs, the test passes. When the SUT produces any result or error that is not prescribed by the specification, the test fails. For a library to comply with the complete specification described herein, it must implement all test specifications, and each test must pass.
All test specifications assume that the Merkle Tree implementation under test uses the SHA-2-256 hashing algorithm as defined in FIPS PUB 180-4 to produce its outputs. The following test cases stipulate a theoretical function Sum(N) that takes in a big endian data slice N and returns the 32 byte SHA-256 hash of N.
Root Signature Tests
- Test Empty Root
- Test Update 1
- Test Update 2
- Test Update 3
- Test Update 5
- Test Update 10
- Test Update 100
- Test Update With Repeated Inputs
- Test Update Overwrite Key
- Test Update Union
- Test Update Sparse Union
- Test Update With Empty Data
- Test Update With Empty Data Performs Delete
- Test Update 1 Delete 1
- Test Update 2 Delete 1
- Test Update 10 Delete 5
- Test Delete Non-existent Key
- Test Interleaved Update Delete
- Test Delete Sparse Union
Test Empty Root
Description:
Tests the default root given no update or delete operations. The input set is described by S = {Ø}.
Inputs:
No inputs.
Outputs:
- The expected root signature:
0x0000000000000000000000000000000000000000000000000000000000000000
Example pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 1
Description:
Tests the root after performing a single update call with the specified input.
Inputs:
- Update the empty tree with
(K, D)where leaf keyK = Sum(0u32)(32 bytes) and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 2
Description:
Tests the root after performing two update calls with the specified inputs.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(1u32)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x8d0ae412ca9ca0afcb3217af8bcd5a673e798bd6fd1dfacad17711e883f494cb
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
root = smt.root()
expected_root = '8d0ae412ca9ca0afcb3217af8bcd5a673e798bd6fd1dfacad17711e883f494cb'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 3
Description:
Tests the root after performing three update calls with the specified inputs.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(1u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(2u32)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x52295e42d8de2505fdc0cc825ff9fead419cbcf540d8b30c7c4b9c9b94c268b7
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x02"), b"DATA")
root = smt.root()
expected_root = '52295e42d8de2505fdc0cc825ff9fead419cbcf540d8b30c7c4b9c9b94c268b7'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 5
Description:
Tests the root after performing five update calls with the specified inputs.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(1u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(2u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(3u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(4u32)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 10
Description:
Tests the root after performing 10 update calls with the specified inputs.
Inputs:
- For each
iin0..10, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x21ca4917e99da99a61de93deaf88c400d4c082991cb95779e444d43dd13e8849
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '21ca4917e99da99a61de93deaf88c400d4c082991cb95779e444d43dd13e8849'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 100
Description:
Tests the root after performing 100 update calls with the specified inputs.
Inputs:
- For each
iin0..100, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x82bf747d455a55e2f7044a03536fc43f1f55d43b855e72c0110c986707a23e4d
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..100 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '82bf747d455a55e2f7044a03536fc43f1f55d43b855e72c0110c986707a23e4d'
expect(hex_encode(root), expected_root).to_be_equal
Test Update With Repeated Inputs
Description:
Tests the root after performing two update calls with the same inputs. The resulting input set is described by S = {A} U {A} = {A}, where {A} is the input. This test expects a root signature identical to that produced by Test Update 1.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree again with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal
Test Update Overwrite Key
Description:
Tests the root after performing two update calls with the same leaf keys but different leaf data. The second update call is expected to overwrite the data originally written by the first update call.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"CHANGE"(bytes, UTF-8)
Outputs:
- The expected root signature:
0xdd97174c80e5e5aa3a31c61b05e279c1495c8a07b2a08bca5dbc9fb9774f9457
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"CHANGE")
root = smt.root()
expected_root = 'dd97174c80e5e5aa3a31c61b05e279c1495c8a07b2a08bca5dbc9fb9774f9457'
expect(hex_encode(root), expected_root).to_be_equal
Test Update Union
Description:
Tests the root after performing update calls with discontinuous sets of inputs. The resulting input set is described by S = [0..5) U [10..15) U [20..25).
Inputs:
- For each
iin0..5, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin10..15, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin20..25, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0x7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 10..15 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 20..25 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
root = smt.root()
expected_root = '7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326'
expect(hex_encode(root), expected_root).to_be_equal
Test Update Sparse Union
Description:
Tests the root after performing update calls with discontinuous sets of inputs. The resulting input set is described by S = [0, 2, 4, 6, 8].
Inputs:
- For each
iin0..5, update the tree with(K, D), where leaf keyK = Sum(i * 2)and leaf dataD = b"DATA"(bytes, UTF-8)
Outputs:
- The expected root signature:
0xe912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
key = &(i as u32 * 2).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
root = smt.root()
expected_root = 'e912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696'
expect(hex_encode(root), expected_root).to_be_equal
Test Update With Empty Data
Description:
Tests the root after performing one update call with empty data. Updating the empty tree with empty data does not change the root, and the expected root remains the default root. The resulting input set is described by S = {Ø} U {Ø} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and empty leaf dataD = b""(0 bytes)
Outputs:
- The expected root signature:
0x0000000000000000000000000000000000000000000000000000000000000000
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"")
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal
Test Update With Empty Data Performs Delete
Description:
Tests the root after performing one update call with arbitrary data followed by a second update call on the same key with empty data. Updating a key with empty data is equivalent to calling delete. By deleting the only key, we have an empty tree and expect to arrive at the default root. The resulting input set is described by S = {0} - {0} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(0u32)and empty leaf dataD = b""(0 bytes)
Outputs:
- The expected root signature:
0x0000000000000000000000000000000000000000000000000000000000000000
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x00"), b"")
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 1 Delete 1
Description:
Tests the root after performing one update call followed by a subsequent delete call on the same key. By deleting the only key, we have an empty tree and expect to arrive at the default root. The resulting input set is described by S = {0} - {0} = {Ø}. This test expects a root signature identical to that produced by Test Empty Root.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Delete
(K)from the tree, where leaf keyK = Sum(0u32)
Outputs:
- The expected root signature:
0x0000000000000000000000000000000000000000000000000000000000000000
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.delete(&sum(b"\x00\x00\x00\x00"))
root = smt.root()
expected_root = '0000000000000000000000000000000000000000000000000000000000000000'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 2 Delete 1
Description:
Tests the root after performing two update calls followed by a subsequent delete call on the first key. By deleting the second key, we have a tree with only one key remaining, equivalent to a single update. This test expects a root signature identical to that produced by Test Update 1.
Inputs:
- Update the empty tree with
(K, D), where leaf keyK = Sum(0u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Update the tree with
(K, D), where leaf keyK = Sum(1u32)and leaf dataD = b"DATA"(bytes, UTF-8) - Delete
(K)from the tree, where leaf keyK = Sum(1u32)
Outputs:
- The expected root signature:
0x39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
smt.update(&sum(b"\x00\x00\x00\x00"), b"DATA")
smt.update(&sum(b"\x00\x00\x00\x01"), b"DATA")
smt.delete(&sum(b"\x00\x00\x00\x01"))
root = smt.root()
expected_root = '39f36a7cb4dfb1b46f03d044265df6a491dffc1034121bc1071a34ddce9bb14b'
expect(hex_encode(root), expected_root).to_be_equal
Test Update 10 Delete 5
Description:
Tests the root after performing 10 update calls followed by 5 subsequent delete calls on the latter keys. By deleting the last five keys, we have a tree with the first five keys remaining, equivalent to five updates. This test expects a root signature identical to that produced by Test Update 5.
Inputs:
- For each
iin0..10, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin5..10, delete(K)from the tree, where leaf keyK = Sum(i)
Outputs:
- The expected root signature:
0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 5..10 {
key = &(i as u32).to_big_endian_bytes()
smt.delete(&sum(key))
}
root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal
Test Delete Non-existent Key
Description:
Tests the root after performing five update calls followed by a subsequent delete on a key that is not present in the input set. This test expects a root signature identical to that produced by Test Update 5.
Inputs:
- For each
iin0..5, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - Delete
(K)from the tree, where leaf keyK = Sum(1024u32)
Outputs:
- The expected root signature:
0x108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..5 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
smt.delete(&sum(b"\x00\x00\x04\x00"))
root = smt.root()
expected_root = '108f731f2414e33ae57e584dc26bd276db07874436b2264ca6e520c658185c6b'
expect(hex_encode(root), expected_root).to_be_equal
Test Interleaved Update Delete
Description:
Tests the root after performing a series of interleaved update and delete calls. The resulting input set is described by [0..5) U [10..15) U [20..25). This test demonstrates the inverse relationship between operations update and delete. This test expects a root signature identical to that produced by Test Update Union.
Inputs:
- For each
iin0..10, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin5..15, delete(K)from the tree, where leaf keyK = Sum(i)from the tree - For each
iin10..20, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin15..25, delete(K)from the tree, where leaf keyK = Sum(i)from the tree - For each
iin20..30, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin25..35, delete(K)from the tree, where leaf keyK = Sum(i)from the tree
Outputs:
- The expected root signature:
0x7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 5..15 {
key = &(i as u32).to_big_endian_bytes()
smt.delete(&sum(key))
}
for i in 10..20 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 15..25 {
key = &(i as u32).to_big_endian_bytes()
smt.delete(&sum(key))
}
for i in 20..30 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 25..35 {
key = &(i as u32).to_big_endian_bytes()
smt.delete(&sum(key))
}
root = smt.root()
expected_root = '7e6643325042cfe0fc76626c043b97062af51c7e9fc56665f12b479034bce326'
expect(hex_encode(root), expected_root).to_be_equal
Test Delete Sparse Union
Description:
Tests the root after performing delete calls with discontinuous sets of inputs. The resulting input set is described by S = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - [1, 3, 5, 7, 9] = [0, 2, 4, 6, 8]. This test expects a root signature identical to that produced by Test Update Sparse Union.
Inputs:
- For each
iin0..10, update the tree with(K, D), where leaf keyK = Sum(i)and leaf dataD = b"DATA"(bytes, UTF-8) - For each
iin0..5, delete(K)from the tree, where leaf keyK = Sum(i * 2 + 1)
Outputs:
- The expected root signature:
0xe912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696
Example Pseudocode:
smt = SparseMerkleTree.new(Storage.new(), sha256.new())
for i in 0..10 {
key = &(i as u32).to_big_endian_bytes()
data = b"DATA"
smt.update(&sum(key), data)
}
for i in 0..5 {
key = &(i as u32 * 2 + 1).to_big_endian_bytes()
smt.delete(&sum(key))
}
root = smt.root()
expected_root = 'e912e97abc67707b2e6027338292943b53d01a7fbd7b244674128c7e468dd696'
expect(hex_encode(root), expected_root).to_be_equal
Defining the ABI
Next, we will define our ABI. ABI stands for Application Binary Interface. In a Sway contract, it serves as an outline of all the functions within the contract. For each function, you need to specify its name, input types, return types, level of storage access, and if it's payable.
The ABI for our contract is structured as follows. Write the ABI provided below into your main.sw file:
<TestAction id="sway-abi" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
Don't be worried about understanding the specifics of each function at this moment. We will dive into detailed explanations in the "Functions" section.
Functions Structure
A function is defined using the fn keyword. In Sway, snake case is the convention, so instead of naming a function myFunction, you would name it my_function.
If the function returns a value, its return type must be defined using a skinny arrow. Additionally, if the function has parameters, their types must also be specified. Semicolons are required at the end of each statement.
If a function either reads from or writes to storage, you need to specify the access level above the function using annotations like #[storage(read)] or #[storage(read, write)].
For functions that are expected to receive funds when called, such as the buy_item function, the #[payable] annotation is required.
Building a Frontend to Interact With Your Contract
To build a frontend application for the counter contract, we'll do the following:
- Install the Fuel Browser Wallet.
- Initialize a React project.
- Install the
fuelsSDK dependency. - Generate contract types.
- Write our frontend code.
- Run our project.
Install the Fuel Browser Wallet
{/install_wallet:example:start/} Our frontend application will allow users to connect with a wallet, so you'll need to have a browser wallet installed.
Before going to the next steps, install the Fuel Wallet extension.
Once you've setup your wallet, click the "Faucet" button in the wallet to get some testnet tokens. {/install_wallet:example:end/}
Initialize a React project
To split our project's contract from frontend code, let's initialize our frontend project: assuming that your terminal is open at your contract's folder /home/user/path/to/counter-contract let's go back up one directory.
cd ..
Now, initialize a React project with TypeScript using Vite.
<TestAction id="create-vite-project" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project' }} />
npm create vite@latest frontend -- --template react-ts
The output should be similar to this:
Scaffolding project in Fuel/fuel-project/frontend...
Done. Now run:
cd frontend
npm install
npm run dev
Installing
Move into the frontend folder and install the dependencies by running:
<TestAction id="install-basic-deps" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project' }} />
cd frontend && npm install
You should now have two folders inside your fuel-project folder: counter-contract and frontend.
<Box.Centered>
</Box.Centered>
Install the fuels SDK dependency
The fuels package includes all the main tools you need to interact with your Sway programs and the Fuel network.
The @fuel-wallet packages include everything you need to interact with user wallets.
fuelsrequires Node version {props.nodeVersion}.
Install the following packages in your frontend folder:
<TestAction id="install-deps" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/frontend' }} />
npm install fuels @fuels/react @fuels/connectors @tanstack/react-query
Generate contract types
The fuels init command generates a fuels.config.ts file that is used by the SDK to generate contract types.
Use the contracts flag to define where your contract folder is located, and the output flag to define where you want the generated files to be created.
Run the command below in your frontend folder to generate the config file:
<TestAction id="fuels_config" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/frontend' }} />
npx fuels init --contracts ../counter-contract/ --output ./src/sway-api
Now that you have a fuels.config.ts file, you can use the fuels build command to rebuild your contract and generate types.
Running this command will interpret the output ABI JSON from your contract and generate the correct TypeScript definitions.
If you see the folder fuel-project/counter-contract/out you will be able to see the ABI JSON there.
Inside the fuel-project/frontend directory run:
<TestAction id="typegen" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/frontend' }} />
npx fuels build
A successful process should print and output like the following:
Building..
Building Sway programs using source 'forc' binary
Generating types..
🎉 Build completed successfully!
Now you should be able to find a new folder fuel-project/frontend/src/sway-api.
Modify the App
Inside the frontend/src folder let's add code that interacts with our contract.
Because we'll be using @fuels/react, first we need to wrap our app with the FuelProvider component.
Add the imports below to the top of your frontend/src/main.tsx file and setup a query client:
<TestAction id="provider-import" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/frontend/src/main.tsx', atLine: 5, }} />
Next, modify your frontend/src/main.tsx file to wrap the App component with the FuelProvider and QueryClientProvider components.
<TestAction id="fuel-wallet-provider" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/frontend/src/main.tsx', atLine: 11, removeLines: [11,12,13,14,15], }} />
Next, change the file fuel-project/frontend/src/App.tsx to:
<TestAction id="app-code" action={{ name: 'writeToFile', filepath: 'guides-testing/fuel-project/frontend/src/App.tsx' }} />
Finally, replace the value of the CONTRACT_ID variable at the top of your App.tsx file with the address of the contract you just deployed.
<TestAction id="app-contract-id" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/frontend/src/App.tsx', atLine: 13, removeLines: [13], useSetData: ' "0x92073699bd78dac70756a9e0e8bca1c7121c7adc4b90570800f0916fe4ac33dd";' }} />
Run your project
Inside the fuel-project/frontend directory run:
<TestAction
id="start-app"
action={{
name: 'runCommand',
preCommand: "pnpm pm2 start 'BROWSER=none
npm run dev
VITE v5.3.5 ready in 108 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
Click the "Connect" button and select the wallet you have installed to connect your wallet.
Once connected, if there are no funds in your wallet, you will see a link to get testnet funds.
If you have testnet ETH on Fuel, you should see the counter value and increment button:
<Box.Centered>
</Box.Centered>
You just built a fullstack dapp on Fuel! ⛽
Here is the repo for this project.
If you run into any problems, a good first step is to compare your code to this repo and resolve any differences.
Tweet us @fuel_network letting us know you just built a dapp on Fuel, you might get invited to a private group of builders, be invited to the next Fuel dinner, get alpha on the project, or something 👀.
Updating The Contract
To develop and test faster, we recommend using the fuels dev command to start a local node and automatically redeploy and generate types for your contract on each change.
Once you're ready to redeploy your contract to the testnet, here are the steps you should take to get your frontend and contract back in sync:
- In your frontend directory, re-run this command:
npx fuels build. - In your contract directory, redeploy the contract.
- In your frontend directory, update the contract ID in your
App.tsxfile.
Need Help?
Get help from the team by posting your question in the Fuel Forum.
<TestAction id="wait-after-start-app" action={{ name: 'wait', timeout: 20000 }} />
<TestAction id="go-to-frontend" action={{ name: 'goToUrl', url: "http://localhost:5173" }} />
<TestAction id="click-connect-button" action={{ name: 'clickByRole', role: "button", elementName: "Connect" }} />
<TestAction id="click-fuel-wallet" action={{ name: 'clickByLabel', label: 'Connect to Fuel Wallet' }} />
<TestAction id="approve-connect" action={{ name: 'walletApproveConnect', }} />
<TestAction id="wait-after-connect" action={{ name: 'wait', timeout: 5000 }} />
<TestAction id="get-initial-count" action={{ name: 'getByLocator-save', locator: "h3 ~ div", }} />
{/* <TestAction id="click-increment-button" action={{ name: 'clickByRole', role: "button", elementName: "Increment" }} /> <TestAction id="approve-txn" action={{ name: 'walletApprove', }} />
<TestAction id="wait-after-approve" action={{ name: 'wait', timeout: 15000 }} />
<TestAction id="reload-after-approve" action={{ name: 'reload', }} />
<TestAction id="wait-after-reload" action={{ name: 'wait', timeout: 7000 }} />
<TestAction id="get-final-count" action={{ name: 'getByLocator-save', locator: "h3 ~ div", }} />
<TestAction id="check-count" action={{ name: 'checkIfIsIncremented', initialIndex: 0, finalIndex: 1 }} /> */}
Writing A Sway Smart Contract
Installation
{/install_help:example:start/}
Having problems? Visit the installation guide or post your question in our forum. {/install_help:example:end/}
Already have fuelup installed?
{/already_installed:example:start/}
If you already have fuelup installed, run the commands below to make sure you are on the most up-to-date toolchain.
fuelup self update
fuelup update
fuelup default latest
{/already_installed:example:end/}
Your First Sway Project
We'll build a simple counter contract with two functions: one to increment the counter, and one to return the value of the counter.
Start by creating a new, empty folder. We'll call it fuel-project.
<TestAction id="create-project-folder" action={{ name: 'runCommand', commandFolder: 'guides-testing' }} />
mkdir fuel-project
Writing the Contract
Move inside of your fuel-project folder:
cd fuel-project
Then create a contract project using forc:
<TestAction id="create-contract" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project' }} />
{/ANCHOR: new_forc_contract/}
forc new counter-contract
{/ANCHOR_END: new_forc_contract/}
You will get this output:
To compile, use `forc build`, and to run tests use `forc test`
----
Read the Docs:
- Sway Book: https://docs.fuel.network/docs/sway
- Forc Book: https://docs.fuel.network/docs/forc
- Rust SDK Book: https://docs.fuel.network/docs/fuels-rs
- TypeScript SDK: https://docs.fuel.network/docs/fuels-ts
Join the Community:
- Follow us @SwayLang: https://twitter.com/SwayLang
- Ask questions on Discourse: https://forum.fuel.network/
Report Bugs:
- Sway Issues: https://github.com/FuelLabs/sway/issues/new
{/This example should include a tree for a new forc project and explain the boilerplate files/}
{/forc_new:example:start/}
Here is the project that forc has initialized:
<TestAction id="contract-tree" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project' }} />
tree counter-contract
counter-contract
├── Forc.toml
└── src
└── main.sw
1 directory, 2 files
forc.toml is the manifest file (similar to Cargo.toml for Cargo or package.json for Node) and defines project metadata such as the project name and dependencies.
{/forc_new:example:end/}
Open your project in a code editor and delete everything in src/main.sw apart from the first line.
Every Sway file must start with a declaration of what type of program the file contains; here, we've declared that this file is a contract. You can learn more about Sway program types in the Sway Book.
<TestAction id="program-type" action={{ name: 'writeToFile', filepath: 'guides-testing/fuel-project/counter-contract/src/main.sw' }} />
Next, we'll define a storage value.
In our case, we have a single counter that we'll call counter of type u64 (a 64-bit unsigned integer) and initialize it to 0.
<TestAction id="storage" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/counter-contract/src/main.sw' }} />
ABI
ABI stands for Application Binary Interface. An ABI defines an interface for a contract. A contract must either define or import an ABI declaration.
It is considered best practice to define your ABI in a separate library and import it into your contract. This allows callers of the contract to import and use the ABI more easily.
For simplicity, we will define the ABI directly in the contract file itself.
<TestAction id="abi" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/counter-contract/src/main.sw' }} />
Implement ABI
Below your ABI definition, you will write the implementation of the functions defined in your ABI.
<TestAction id="impl" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/counter-contract/src/main.sw' }} />
storage.counter.read()is an implicit return and is equivalent toreturn storage.counter.read();.
Here's what your code should look like so far:
File: ./counter-contract/src/main.sw
<TestAction id="entire-contract" action={{ name: 'compareToFile', filepath: 'guides-testing/fuel-project/counter-contract/src/main.sw' }} />
Build the Contract
Navigate to your contract folder:
cd counter-contract
Then run the following command to build your contract:
<TestAction id="build-contract" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/counter-contract' }} />
forc build
Compiled library "core".
Compiled library "std".
Compiled contract "counter-contract".
Bytecode size: 84 bytes.
Let's have a look at the content of the counter-contract folder after building:
<TestAction id="built-contract-tree" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/counter-contract' }} />
tree .
.
├── Forc.lock
├── Forc.toml
├── out
│ └── debug
│ ├── counter-contract-abi.json
│ ├── counter-contract-storage_slots.json
│ └── counter-contract.bin
└── src
└── main.sw
3 directories, 6 files
We now have an out directory that contains our build artifacts such as the JSON representation of our ABI and the contract bytecode.
Testing your Contract with Rust
Don't want to test with Rust? Skip this section and jump to Deploy the Contract.
We will start by adding a Rust integration test harness using a Cargo generate template.
If you don't already have Rust installed, you can install it by running this command:
Next, if you don't already have it installed, let's install cargo generate:
cargo install cargo-generate --locked
Now, let's generate the default test harness with the following command:
<TestAction id="cargo-generate-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/counter-contract' }} />
cargo generate --init fuellabs/sway templates/sway-test-rs --name counter-contract
⚠️ Favorite `fuellabs/sway` not found in config, using it as a git repository: https://github.com/fuellabs/sway.git
🔧 Destination: /home/user/path/to/counter-contract ...
🔧 project-name: counter-contract ...
🔧 Generating template ...
🔧 Moving generated files into: `/home/user/path/to/counter-contract`...
✨ Done! New project created /home/user/path/to/counter-contract
Let's have a look at the result:
<TestAction id="cargo-test-tree" action={{ name: 'runCommand', commandFolder: 'guides-testing/fuel-project/counter-contract' }} />
tree .
.
├── Cargo.toml
├── Forc.lock
├── Forc.toml
├── out
│ └── debug
│ ├── counter-contract-abi.json
│ ├── counter-contract-storage_slots.json
│ └── counter-contract.bin
├── src
│ └── main.sw
└── tests
└── harness.rs
4 directories, 8 files
{/rust_harness:example:start/} We have two new files!
- The
Cargo.tomlis the manifest for our new test harness and specifies the required dependencies includingfuels(the Fuel Rust SDK). - The
tests/harness.rscontains some boilerplate test code to get us started, though doesn't call any contract methods just yet.
Open your Cargo.toml file and check the version of fuels used under dev-dependencies. Change the version to 0.66.1 if it's not already:
[dev-dependencies]
fuels = "0.66.1"
tokio = { version = "1.12", features = ["rt", "macros"] }
{/rust_harness:example:end/}
Now that we have our default test harness, let's add a useful test to it.
At the bottom of test/harness.rs below the can_get_contract_id() test, add the test_increment test function below to verify that the value of the counter gets incremented:
<TestAction id="test-harness" action={{ name: 'modifyFile', filepath: 'guides-testing/fuel-project/counter-contract/tests/harness.rs', addSpacesBefore: 1, }} />
Here is what your file should look like:
File: ./counter-contract/tests/harness.rs
<TestAction id="final-test-harness" action={{ name: 'compareToFile', filepath: 'guides-testing/fuel-project/counter-contract/tests/harness.rs' }} />
Run cargo test in the terminal:
cargo test
If all goes well, the output should look as follows:
...
running 2 tests
test can_get_contract_id ... ok
test test_increment ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s
Deploy the Contract
It's now time to deploy . We will show how to do this using forc from the command line, but you can also do it using the Rust SDK or the TypeScript SDK.
In order to deploy a contract, you need to have a wallet to sign the transaction and coins to pay for gas. Fuelup will guide you in this process.
Setting up a local wallet
You can get test funds using the faucet.
Deploy To Testnet
Now, you can deploy the contract to the latest testnet with the forc deploy command.
forc deploy --testnet
{/forc_wallet:example:start/} The terminal will ask for the password of the wallet:
Please provide the password of your encrypted wallet vault at "~/.fuel/wallets/.wallet":
Once you have unlocked the wallet, the terminal will show a list of the accounts:
Account 0 -- fuel18caanqmumttfnm8qp0eq7u9yluydxtqmzuaqtzdjlsww5t2jmg9skutn8n:
Asset ID Amount
0000000000000000000000000000000000000000000000000000000000000000 499999940
Just below the list, you'll see this prompt:
Please provide the index of account to use for signing:
Then you'll enter the number of the account of preference and press Y when prompted to accept the transaction.
Finally, you will get back the network endpoint where the contract was deployed, a Contract ID and the block where the transaction was signed.
{/forc_wallet:example:end/}
Save the Contract ID, as you'll need this later to connect the frontend.
Contract counter-contract Deployed!
Network: https://testnet.fuel.network
Contract ID: 0x8342d413de2a678245d9ee39f020795800c7e6a4ac5ff7daae275f533dc05e08
Deployed in block 0x4ea52b6652836c499e44b7e42f7c22d1ed1f03cf90a1d94cd0113b9023dfa636
Congrats, you have completed your first smart contract on Fuel ⛽
Here is the repo for this project. If you run into any problems, a good first step is to compare your code to this repo and resolve any differences.
Tweet us @fuel_network letting us know you just built a dapp on Fuel, you might get invited to a private group of builders, be invited to the next Fuel dinner, get alpha on the project, or something 👀.
Need Help?
Get help from the team by posting your question in the Fuel Forum.
Checkpoint
Sway Contract Checkpoint
If you have followed the previous steps correctly your main.sw marketplace contract should look like this:
Building the contract
Here's a polished version of your instructions:
To format your contract, execute the command:
<TestAction id="format-contract" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/sway-programs/contract' }} />
forc fmt
To compile your contract, navigate to the contract folder and run:
<TestAction id="build-contract" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/sway-programs/contract' }} />
forc build
Congratulations! You've successfully written a full contract in Sway!
Post-compilation, the system will automatically generate abi.json, storage_slots.json, and contract.bin. You can locate these files in the following directory:
contract/out/debug/*
Deploying the contract
For detailed steps on deploying this contract, refer to the official Fuel developer counter dapp guide: Deploy the Contract
To deploy, use the following command if you've already set up the forc-wallet and have testnet funds in your account. If not, follow the instructions above.
forc deploy --testnet
After deploying, you'll be able to find your contract ID in the contract/out/deployments folder. You'll need this for frontend integration.
Checkpoint
If you have followed the steps properly, your predicate main.sw should look like the code below:
Building the predicate
To format your contract, execute the command:
<TestAction id="format-predicate" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate/predicate' }} />
forc fmt
To get the predicate root, go to the predicate folder and run:
<TestAction id="build-predicate" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate/predicate' }} />
forc build
Your predicate root should be exactly:
0x2d5e1058a695d6fd8bf30dfa1d8e987f99c9c99a6dd614103d2b4b0f11c1eb40
That's it! You've created your first stateless decentralized application, and we didn't even have to deploy it!
Configurables
Configurables are special constants that can be modified at compile time. This is where we can define the signers responsible for protecting the funds in the predicate as well as the number of signatures required.
This information can later be configured with SDKs before building a transaction.
<TestAction id="sway-configurable" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw' }} />
Imagine you are a multisig provider assisting businesses and users in setting up their own multisigs. You wouldn't want to hard-code these details every time but rather provide a few parameters that users can configure themselves.
Smart Contract Quickstart
Getting started with Fuel as a smart contract developer is as simple as:
- Installing
fuelup - Generating a counter contract
- Building the contract
- Setting up a local wallet
- Deploying the contract
Installation
Already have fuelup installed?
Generating a counter contract
Run the command below to generate a counter contract in Sway:
{/* TODO: Replace when forc template command is stable
forc template --template-name counter counter-contract
*/}
{/TODO: Remove when forc template command is stable/}
<TestAction id="create-contract-project" action={{ name: 'runCommand', commandFolder: 'guides-testing/' }} />
The contract will be in the src/main.sw file.
Building the contract
To build a contract, move inside the counter-contract folder:
cd counter-contract
{/TODO: Remove when forc template command is stable/}
Copy and paste the code below into your src/main.sw file
<TestAction id="copy-contract" action={{ name: 'writeToFile', filepath: 'guides-testing/counter-contract/src/main.sw' }} />
Next, run the forc build command:
<TestAction id="build-contract" action={{ name: 'runCommand', commandFolder: 'guides-testing/counter-contract' }} />
forc build
Setting up a local wallet
You can get test funds using the faucet.
Deploying the contract
To deploy the contract to the testnet, you can run:
forc deploy --testnet
Next Steps
Ready to learn more? Check out the following resources:
- Learn the step-by-step instructions for how to build a full-stack counter contract dapp
- Build a full-stack marketplace dapp with the Intro to Sway guide
- Try building with Predicates
- Read the Sway docs
Counter React Dapp
This guide includes step-by-step instructions for how to
- Write a counter smart contract in Sway
- Write a test in Rust
- Deploy to Fuel's testnet
- Build a frontend
- Integrate a wallet
Before we begin, it may be helpful to understand the terminology that will be used throughout the docs and how they relate to each other:
- Fuel: the Fuel blockchain.
- FuelVM: the virtual machine powering Fuel.
- Sway: the domain-specific language crafted for the FuelVM; it is inspired by Rust.
- Forc: the build system and package manager for Sway, similar to Cargo for Rust.
Debugging with Scripts
In every aspect of development, trade-offs are inevitable. As previously mentioned, logging is not feasible when dealing with predicates, since predicates are required to be pure. This raises an important question: how do we debug predicates?
Sway, a programming language, categorizes programs into four types, with scripts being one of them. Unlike predicates, scripts allow for shared logic.
Let's move outside our MultiSig project
cd ../..
and create a separate project called predicate-script-logging.
<TestAction id="create-predicate-script-logging" action={{ name: 'runCommand', commandFolder: 'guides-testing/' }} />
forc new --predicate predicate-script-logging
Copy and paste this new predicate in your src/main.sw. Attempting to build this predicate will result in an error, indicating that logging is an invalid operation.
However, let's try switching the program type from a predicate to a script.
Your code should now look like this:
<TestAction id="sway-program-type" action={{ name: 'writeToFile', filepath: 'guides-testing/predicate-script-logging/src/main.sw' }} />
Now, if we attempt to build our script, it should compile without any issues.
<TestAction id="build-predicate" action={{ name: 'runCommand', commandFolder: 'guides-testing/predicate-script-logging/' }} />
forc build
Next, we'll generate a Rust template to see it in action!
Defining Error Handling
Enumerations, commonly referred to as enums, are a type that can represent one of several possible variants. Within our contract, we can employ enums to craft custom error messages, facilitating more precise error handling within functions.
Copy the custom error block into your main.sw file:
<TestAction id="sway-errors" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
Within our contract, we can anticipate various scenarios where we'd want to throw an error and halt the transaction:
- Someone might attempt to pay for an item using an incorrect currency.
- An individual could try to purchase an item without possessing sufficient coins.
- Someone other than the owner might attempt to withdraw funds from the contract.
For each situation, we can define specific return types for the errors:
- For the
IncorrectAssetIderror, we can return the submitted asset id, which is of typeAssetId. - In the case of the
NotEnoughTokenserror, we can define the return type asu64to indicate the number of coins involved. - For the
OnlyOwnererror, we can utilize theIdentityof the message sender as the return value.
Congrats on completing the intro to Sway guide! 🎉
Encountering issues? A useful initial step is to align your code with the repository's and address any discrepancies. Check out the project's repository here. 🔍
Excited about your achievement? Share it with us on Twitter @fuel_network. By doing so, you could gain access to an exclusive community of developers, receive an invitation to our upcoming Fuel dinner, or even get insider information about the project. Keep an eye out for surprises! 👀
Keep building on Fuel
Ready to keep building? You can dive deeper into Sway and Fuel in the resources below:
✨ Build a frontend with the TypeScript SDK
🦀 Write tests with the Rust SDK
📖 See Example Sway Applications
🐦 Follow Sway Language on Twitter
❓ Ask questions in the Fuel Forum
Next.js Fullstack Quickstart
A non-NextJs app will be available once this issue is resolved
Getting started with Fuel as a frontend or fullstack developer is as simple as:
Installation
Already have fuelup installed?
Generating a counter dapp
You can generate a full-stack counter dapp in seconds with the create fuels CLI:
pnpm create fuels
npm create fuels
Running the project locally
Move into the project directory. Assuming you named the project my-fuel-project, you can run:
cd my-fuel-project
Next, run the following command to start a local development node:
pnpm fuels:dev
npm run fuels:dev
The local endpoint for node will be http://localhost:4000/v1/graphql.
Next, open a new terminal in the project directory, and run the following command to start the frontend:
pnpm dev
npm run dev
The frontend will be running at http://localhost:3000.
While the local node is running, any changes you make to the Sway contract inside the sway-contract folder will automatically trigger several updates:
- The contract gets rebuilt using the
forc buildcommand. - The contract will be redeployed to the local node using the
forc deploycommand . - New TypeScript types for the contract and a file called
contract-ids.jsonwith the new contract ID will be generated in thesrc/sway-apifolder.
This means you don't need to worry about updating the contract ID, ABI, or TypeScript types while you develop.
Next Steps
Ready to learn more? Check out the following resources:
- Learn the step-by-step instructions for how to build and deploy a full-stack counter contract dapp
- Learn more about the fuels CLI
- Learn about wallet connectors
Fuel Connectors
Fuel is built to enable connectivity with wallets from any chain. This is achieved through a tool called Fuel Connectors, which is used by developers to connect your wallet of choice with applications on the Fuel network.
Some behavior might surprise you if you've never worked with cross-chain wallets before, and this document walks through some of those surprises, and why they are completely normal.
Github Codespace
Introduction
The way to think about Github Codespaces is essentially VSCode in a browser. It’s a remote development environment that is extremely easy to spin up. While not all VS Code plugins are supported, the Sway LSP plugin is supported and works out of the box.
How to set up for a new repo
-
Create a
devcontainer.jsonfile. The easiest way is by navigating to the repo and clicking Code → … → Configure dev container<Box.Centered>
</Box.Centered> -
Edit the file to include the following features:
"features": { "ghcr.io/devcontainers/features/common-utils:1": {}, "ghcr.io/FuelLabs/devcontainer-features/fuelup:1.0.1": {}, } -
Add any plugins that you want to be installed for this repo under “customizations”.
"customizations": { "vscode": { "extensions": [ "fuellabs.sway-vscode-plugin" ] } }Here are examples that include the Sway LSP plugin.
3.1.
https://github.com/FuelLabs/sway/blob/master/.devcontainer/devcontainer.json3.2.
https://github.com/FuelLabs/quickstart/blob/master/.devcontainer/devcontainer.json
How to start a codespace
-
Navigate to the repo that has Github Codespaces configured.
-
Choose Code → Create codespace on master
<Box.Centered>
</Box.Centered> -
This will open a new tab with your codespace. It can take several minutes to start up.
3.1. You now have a fully functional remote development environment with the Fuel toolchain installed! You can use
forcto build and deploy Sway code, orfuelupto manage the toolchain version. You also have the Sway LSP plugin with full feature support for Sway, like syntax highlighting, hover docs, go-to definitions, etc.3.2. Note: if you are working on a large repository and find the codespace is running slow, you can configure it to use a larger instance by clicking Code → … → change machine type on a running instance, or starting a new instance with Code → … → New with options.
Pricing & billing
You will be required to enter billing information, however there is a substantial free tier.
What's next?
Now you are ready to start building with Fuel.
👉 Check out the counter dapp guide to deploy your first smart contract.
Defining the Contract Functions
Finally, it's time to compose our contract functions. Begin by copying and pasting the ABI we outlined earlier. It's crucial to ensure that the functions within the contract exactly align with the ABI; otherwise, the compiler will generate an error. Now, substitute the semicolons at the conclusion of each function with curly brackets. Also, modify abi SwayStore to impl SwayStore for Contract, as demonstrated below:
This guide will first show each of the completed functions above. Then, we'll break it down to explain each part, clarify specific syntax, and discuss fundamental concepts in Sway.
1. Listing an item
Our first function enables sellers to list an item for sale. They can specify the item's price and provide a string that references externally-stored data about the item.
Updating list storage
The initial step involves incrementing the item_counter in storage, which will serve as the item's ID. In Sway, all storage variables are contained within the storage keyword, ensuring clarity and preventing conflicts with other variable names. This also allows developers to easily track when and where storage is accessed or altered. The standard library in Sway provides read(), write(), and try_read() methods to access or manipulate contract storage. It's advisable to use try_read() when possible to prevent potential issues arising from accessing uninitialized storage. In this case, we read the current count of listed items, modify it, and then store the updated count back into storage, making use of the well-organized and conflict-free storage system.
When a function returns an Option or Result type, we can use unwrap() to access its inner value. For instance, try_read() returns an Option type. If it yields Some, we get the contained value; but if it returns None, the contract call is immediately halted.
Getting the message sender
Next, we'll retrieve the Identity of the account listing the item.
To obtain the Identity, utilize the msg_sender function from the standard library. The msg_sender represents the address of the entity (be it a user address or another contract address) initiating the current function call.
This function yields a Result, which is an enum type that can either be OK or an error. Use the Result type when anticipating a value that might result in an error. For example in the case of msg_sender when an external caller is involved and the coin input owners differ, identifying the caller becomes impossible. In such edge cases, an Err(AuthError) is returned.
enum Result<T, E> {
Ok(T),
Err(E),
}
In Sway, you can define a variable using either let or const.
To retrieve the inner value, you can use the unwrap method. It returns the contained value if the Result is OK and triggers a panic if the result indicates an error.
Creating a new item
You can instantiate a new item using the Item struct. Use the item_counter value from storage as the ID, set the price and metadata based on the input parameters, and initialize total_bought to 0.
Since the owner field requires an Identity type, you should utilize the sender value obtained from msg_sender().
Updating a StorageMap
Lastly, add the item to the item_map within storage using the insert method. Utilize the same ID for the key and designate the item as its corresponding value.
2. Buying an item
Next, we aim to allow buyers to purchase listed items. To achieve this, we'll need to:
- Accept the desired item ID from the buyer as a function parameter.
- Ensure the buyer is paying the correct price with valid coins.
- Increase the
total_boughtcount for that item. - Deduct a contract fee from the item's cost and transfer the remaining amount to the seller.
Verifying payment
We can use the msg_asset_id function from the standard library to obtain the asset ID of the coins being transferred in the transaction.
Next, we'll utilize the require statement to ensure the sent asset is the correct one.
The require statement accepts two arguments: a condition, and a value that's logged when the condition is false. Should the condition evaluate as false, the entire transaction is rolled back, leaving no changes.
In this case, the condition checks if the asset_id matches the base asset ID — the default asset associated with the base blockchain - using AssetId::base(). For example, if the base blockchain is Ethereum, the base asset would be ETH.
If there's a mismatch in the asset, for instance, if someone attempts to purchase an item using a different coin, we'll trigger the custom error previously defined, passing along the asset_id.
Next, we can use the msg_amount function from the standard library to retrieve the quantity of coins transmitted by the buyer within the transaction.
To ensure the sent amount is not less than the item's price, we should retrieve the item details using the item_id parameter.
To obtain a value for a specific key in a storage map, the get method is handy, wherein the key value is passed. For mapping storage access, the try_read() method is utilized. As this method produces a Result type, the unwrap method can be applied to extract the item value.
In Sway, all variables are immutable by default, whether declared with let or const. To modify the value of any variable, it must be declared mutable using the mut keyword. Since we plan to update the item's total_bought value, it should be defined as mutable.
Additionally, it's essential to ensure that the quantity of coins sent for the item isn't less than the item's price.
Updating buy storage
We can increase the item's total_bought field value and subsequently reinsert it into the item_map. This action will replace the earlier value with the revised item.
Transferring payment
Lastly, we can process the payment to the seller. It's recommended to transfer assets only after all storage modifications are completed to prevent reentrancy attacks.
For items reaching a specific price threshold, a fee can be deducted using a conditional if statement. The structure of if statements in Sway mirrors that in JavaScript except for the brackets ().
In the aforementioned if-condition, we assess if the transmitted amount surpasses 100,000,000. For clarity in large numbers like 100000000, we can represent it as 100_000_000. If the foundational asset for this contract is ETH, this equates to 0.1 ETH given that Fuel uses a 9 decimal system.
Should the amount exceed 0.1 ETH, a commission is determined and then deducted from the total.
To facilitate the payment to the item's owner, the transfer function is utilized. This function, sourced from the standard library, requires three parameters: the Identity to which the coins are sent, the coin's asset ID, and the coin quantity for transfer.
3. Get an item
To get the details for an item, we can create a read-only function that returns the Item struct for a given item ID.
To return a value in a function, you can use the return keyword, similar to JavaScript. Alternatively, you can omit the semicolon in the last line to return that value like in Rust.
fn my_function_1(num: u64) -> u64{
// returns the num variable
return num;
}
fn my_function_2(num: u64) -> u64{
// returns the num variable
num
}
4. Initialize the owner
This method sets the owner's Identity for the contract but only once.
To ensure that this function can only be called once, specifically right after the contract's deployment, it's imperative that the owner's value remains set to None. We can achieve this verification using the is_none method, which assesses if an Option type is None.
It's also important to note the potential risk of front running in this context this code has not been audited.
To assign the owner as the message sender, it's necessary to transform the Result type into an Option type.
Finally, we'll return the Identity of the message sender.
5. Withdraw funds
The withdraw_funds function permits the owner to withdraw any accumulated funds from the contract.
First, we'll ensure that the owner has been initialized to a specific address.
Next, we'll verify that the individual attempting to withdraw the funds is indeed the owner.
Additionally, we can confirm the availability of funds for withdrawal using the this_balance function from the standard library. This function returns the current balance of the contract.
Lastly, we'll transfer the entire balance of the contract to the owner.
6. Get the total items
The final function we'll introduce is get_count. This straightforward getter function returns the value of the item_counter variable stored in the contract's storage.
Review
The SwayStore contract implementation in your main.sw should now look like this, following everything else we have previously written:
<TestAction id="sway-functions" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
Imports
The Sway standard library provides several utility types and methods we can use in our contract. To import a library, you can use the use keyword and ::, also called a namespace qualifier, to chain library names like this:
You can also group together imports using curly brackets:
For this contract, here is what needs to be imported. Copy this to your main.sw file:
<TestAction id="sway-import" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
We'll go through what each of these imports does as we use them in the next steps.
Imports
The predicate keyword is used to identify that the program is a predicate.
<TestAction id="sway-program-type" action={{ name: 'writeToFile', filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw' }} />
We're going to utilize the Sway standard library in our predicate. Delete the template code except for the predicate keyword and copy in the imports below:
<TestAction id="sway-import" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw' }} />
Transactions
To construct the MultiSig, it's essential for us to obtain three specific components from the transaction through the standard library:
- Transaction Witness Data: We'll use this to attach signatures on the transaction.
- Transaction Witness Count: This will help us determine the number of signatures attached.
- Transaction ID: The hash of the transaction.
Constants
From the constants library, we'll be using ZERO_B256 as a placeholder.
Signatures
We'll need b512 because signatures are of type b512.
Elliptical Curve
Lastly, we will be using ec_recover_address, short for elliptical curve recovery address. It's a function that allows us to cryptographically recover the address that signed a piece of data:
#![allow(unused)] fn main() { signing_address = ec_recover_address(signed_data, original_data) }
This step is crucial for safeguarding the funds and ensuring that only the correct wallets can provide the necessary signatures.
Toolchain installation
This guide will help you to install the Fuel toolchain binaries and prerequisites.
This guide covers the following topics:
- Installing the Fuel toolchain using
fuelup - Changing your default toolchain
- Setting up a local wallet
- Installing Rust
Installing the Fuel toolchain using fuelup
fuelup is the official package manager for Fuel that installs the Fuel toolchain
from the official release channels, enabling you to easily switch between different
toolchains and keep them updated. It makes building and maintaining Sway applications simpler with forc and fuel-core for common platforms.
💡 Check out the fuelup docs for more information.
Running fuelup-init
{/install_fuelup:example:start/}
To install the Fuel toolchain, you can use the fuelup-init script.
This will install forc, forc-client, forc-fmt, forc-lsp, forc-wallet as well as fuel-core in ~/.fuelup/bin.
{/install_fuelup:example:end/}
👉 Just paste the following line in your terminal and press Enter.
{/ANCHOR: install_fuelup_command/}
curl https://install.fuel.network | sh
{/ANCHOR_END: install_fuelup_command/}
🚧 Be aware that currently we do not natively support Windows. If you wish to use
fuelupon Windows, please use Windows Subsystem for Linux.
Setup PATH
Once the script is downloaded, it will be executed automatically.
The fuelup-init script will prompt you with the question below:
fuelup uses "/home/username/.fuelup" as its home directory to manage the Fuel toolchain, and will install binaries there.
To use the toolchain, you will have to configure your PATH, which tells your machine where to locate `fuelup` and the Fuel toolchain.
If permitted, fuelup-init will configure your PATH for you by running the following:
echo "export PATH="$HOME/.fuelup/bin:$PATH"" >> /home/username/.bashrc
Would you like fuelup-init to modify your PATH variable for you? (N/y)
👉 Press the Y key in your terminal and press Enter to modify your PATH.
Checking the installation
After allowing the fuelup-init script to modify your PATH variable, you will see a lot of information about packages being downloaded and installed. If everything goes as expected you will see the following message:
The Fuel toolchain is installed and up to date
fuelup 0.20.0 has been installed in /home/username/.fuelup/bin.
To fetch the latest toolchain containing the forc and fuel-core binaries, run 'fuelup toolchain install latest'.
To generate completions for your shell, run 'fuelup completions --shell=SHELL'.
👉 Use fuelup --version any time to check which version of the package you are using.
fuelup --version
That will output your current fuelup version:
fuelup 0.21.0
VSCode extensions
{/install_VSCode_extensions:example:start/} If you're using VSCode, we recommend installing the Sway extension. {/install_VSCode_extensions:example:end/}
Changing your default toolchain
Just as in Rust, Fuel supports multiple toolchains.
A toolchain is a collection of tools (such as the compiler, LSP, etc).
By default, fuelup includes a series of packages tested to work with each other, providing a reliable set of tools.
The default toolchain configured when you install fuelup is the latest toolchain, which is the stable toolchain compatible with the current {props.fuelTestnetInlineCode} network.
Updating fuelup
Make sure you have the latest version of fuelup so you can access the latest features and have the best performance.
👉 Update fuelup by running the following command:
fuelup self update
Then you will get an output like this:
Fetching binary from https://github.com/FuelLabs/fuelup/releases/download/v0.19.5/fuelup-0.19.5-aarch64-apple-darwin.tar.gz
Downloading component fuelup without verifying checksum
Unpacking and moving fuelup to /var/folders/tp/0l8zdx9j4s9_n609ykwxl0qw0000gn/T/.tmpiNJQHt
Moving /var/folders/tp/0l8zdx9j4s9_n609ykwxl0qw0000gn/T/.tmpiNJQHt/fuelup to /Users/.fuelup/bin/fuelup
Using the latest toolchain
To properly interact with the testnet network it is necessary to use the latest toolchain, which is installed by default.
👉 Run the following command to verify the installation of latest toolchain:
fuelup show
If the toolchain was successfully installed, you will see this output:
installed toolchains
--------------------
latest-x86_64-unknown-linux-gnu (default)
active toolchain
-----------------
latest-x86_64-unknown-linux-gnu (default)
...
Installing nightly toolchain
In case you want to try out the unreleased features of the Fuel toolchain, you can install the nightly toolchain.
👉 Run the following command to install the nightly toolchain:
fuelup toolchain install nightly
If the toolchain was successfully installed, you will see this output:
The Fuel toolchain is installed and up to date
The toolchain was installed correctly, however is not in use yet. Next, you need to configure fuelup to use the nightly toolchain as the default.
{/set_default_testnet:example:start/}
👉 Set nightly as your default toolchain with the following command:
{/set_default_testnet:example:end/}
{/ANCHOR: set_default_testnet_command/}
fuelup default nightly
{/ANCHOR_END: set_default_testnet_command/}
You will get the following output indicating that you have successfully set nightly as your default toolchain.
default toolchain set to nightly
Checking your current toolchain
Sometimes you might end up using multiple toolchains at once. Don't worry if you get confused about which toolchain you are using, since you can check your current toolchain anytime.
👉 Run the fuelup show command to see the toolchain and the versions of each tool you are using.
fuelup show
This command will give you the following output
active toolchain
-----------------
beta-4-x86_64-unknown-linux-gnu (default)
forc : 0.45.0
- forc-client
- forc-deploy : 0.45.0
- forc-run : 0.45.0
- forc-doc : 0.45.0
- forc-explore : 0.28.1
- forc-fmt : 0.45.0
- forc-index : 0.20.7
- forc-lsp : 0.45.0
- forc-tx : 0.45.0
- forc-wallet : 0.3.0
fuel-core : 0.20.4
fuel-core-keygen : Error getting version string
fuels versions
---------------
forc : 0.45
forc-wallet : 0.45
Setting up a local wallet
{/forc_wallet_setup:example:start/}
The forc-wallet plugin is packaged alongside the default distributed toolchains when installed using fuelup, so you should already have this installed if you've followed the instructions above.
To initialize a new wallet with forc-wallet, you can run the command below:
forc wallet new
After typing in a password, be sure to save the mnemonic phrase that is output.
Next, create a new wallet account with:
forc wallet account new
With this, you'll get a fuel address that looks something like this: fuel1efz7lf36w9da9jekqzyuzqsfrqrlzwtt3j3clvemm6eru8fe9nvqj5kar8.
If you need to list your accounts, you can run the command below:
forc wallet accounts
{/forc_wallet_setup:example:end/}
Installing Rust
{/install_rust:example:start/}
If you want to develop with the fuels Rust SDK, you will need to install Rust on your machine. To install Rust, you can use the rustup tool.
{/install_rust:example:end/}
You don't need to install Rust if you don't plan on using the Rust SDK.
Run the following command in your shell; this downloads and runs rustup-init.sh, which in turn downloads and runs the correct version of the rustup-init executable for your platform.
{/ANCHOR: install_rust_command/}
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
{/ANCHOR: install_rust_command/}
Check the official Rust documentation to get more information on installing the Rust toolchain.
What's next?
Now you are ready to start building with Fuel.
👉 Check out the counter dapp guide to deploy your first smart contract.
Beyond the basics
Custom toolchains
You can create your own set of specific versions, this is known as 'custom toolchains'.
👉 Visit the Fuelup docs to learn how to set up your own custom toolchains.
Build from source
You can always build the Fuel packages from source.
👉 Visit the Fuelup docs to get more details about other types of installation.
Github Codespaces
It's always possible to run a development environment in the browser.
👉 Visit our guide on Github Codespaces to use the Fuel toolchain in the browser.
Predicates 101: Building Stateless DeFi Applications
Predicates are Fuel's approach to stateless account abstraction. In the blockchain space, we are constantly faced with the exponential growth of state bloat that just isn't sustainable in the long term. In the Ethereum ecosystem, every contract deployed requires state storage on the blockchain indefinitely. To help with blockchain scalability, we need to consider different approaches to redefine state-minimized applications that are fundamental to the world of decentralized finance.
This tutorial will specifically concentrate on the predicate program type, one of the four program types in the Sway language and how we can solve this ever growing problem.
What are Predicates?
To define Predicates into one sentence:
Predicates are stateless programs that return true or false.
A predicate is represented by an Address type, identical to any EOA (Externally Owned Account) created by a private key. The bytecode of the program can be deterministically hashed and represented as an ordinary address, all calculated off-chain. Therefore, when this address contains assets, ANYONE can spend the assets locked behind the predicate if they can evaluate the predicate to be true. It might be helpful to think of the code as the private key to the wallet.
If this concept is still a bit unclear don't worry!, let's explore a simple example in the next part of the project setup.
📚 Sway Standard Library: A native library equipped with useful types and methods.
🧑🔧 Fuelup: The official Fuel toolchain manager aids in installing and managing different versions.
🦀 Fuel's Rust SDK: Test and interact with your Sway contracts using Rust.
⚡ Fuel's TypeScript SDK: Test and interact with your Sway contracts using TypeScript.
Introduction to Sway Language for JavaScript Developers (React App)
If you're familiar with JavaScript and have a basic understanding of blockchain fundamentals, you can swiftly grasp how to build full-stack decentralized applications on Fuel using Sway. Once you get a handle on Sway's essentials, you'll be able to begin building your own dapp.
Within this tutorial, we will be crafting a Sway contract for an online marketplace similar to Amazon, where:
- Sellers can list products.
- Buyers can purchase those products.
<Box.Centered>
</Box.Centered>
One of the compelling features of smart contracts is their immutability and permissionless nature. This ensures that no single entity can modify or adjust the rules of the marketplace after its deployment. For instance, once a product is listed in the contract, the deployer cannot suddenly alter its status. Similarly, if a commission amount is hardcoded into the contract, it remains fixed, preventing any changes to the commission charged for products.
Furthermore, the contract remains open for interaction by anyone. This universality allows any individual to engage with the marketplace using their custom frontend without requiring permission.
In this tutorial, our attention will be specifically directed towards the contract program type. This is just one of the four program types inherent to the Sway language.
What is Sway?
Sway is a strongly-typed programming language based on Rust, designed for authoring smart contracts on the Fuel blockchain. It leverages Rust's performance, control, and safety attributes, making it suitable for a blockchain virtual machine environment that's optimized for gas costs and contract security.
Sway is bolstered by a robust compiler and toolchain. These tools simplify the complexities and ensure that your code is efficient, secure, and performs optimally.
What truly distinguishes Sway is the exceptional suite of tools built around it. These tools are meticulously designed to convert contracts into full-stack dapps, ensuring a seamless and unparalleled developer experience.
📚 Sway Standard Library: A native library equipped with useful types and methods.
🧑🔧 Fuelup: The official Fuel toolchain manager aids in installing and managing different versions.
🦀 Fuel's Rust SDK: Test and interact with your Sway contracts using Rust.
⚡ Fuel's TypeScript SDK: Test and interact with your Sway contracts using TypeScript.
Logging in Rust tests
Generating a Test Template in Rust
To create your own test template using Rust, follow these steps with cargo-generate in the script project directory:
- Install
cargo-generate:
cargo install cargo-generate --locked
{/markdownlint-disable/} 2. Generate the template: {/markdownlint-disable/}
<TestAction id="cargo-generate-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/predicate-script-logging/' }} />
cargo generate --init fuellabs/sway templates/sway-test-rs --name sway-store
Logging
We previously covered imports and setting up the predicate in an earlier introduction to Sway tutorial, specifically in the Rust testing section. If you haven't checked that out yet, I highly recommend doing so.
Copy and paste the rust test below:
<TestAction id="sway-program-type" action={{ name: 'writeToFile', filepath: 'guides-testing/predicate-script-logging/tests/harness.rs' }} />
Now, I want to draw your attention to a specific portion of the code here:
We can now call decode_logs to extract our secret number, something we weren't able to do when testing with predicates.
To enable print outputs to appear in the console during tests, you can use the nocapture flag.
<TestAction id="cargo-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/predicate-script-logging/' }} />
cargo test -- --nocapture
Remembering this method is essential when developing more complex predicates, especially as debugging becomes increasingly challenging.
Main
Now that we have all the components, let's put them together!
We simply call the function across all the multisigs, tallying the number of valid signatures to see if it meets the threshold set in the configuration. It must return true or false in order to determine if assets can be unlocked.
<TestAction id="sway-main" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw' }} />
Non-technical Guide
1. Your Wallet Address Will Look Different
This is expected, and it's because Fuel Connectors use a technical tool called a predicate to interact with your wallet. The address you see is the address of the predicate.
This predicate is kind of like a post office box that automatically forwards to your home address and visa versa. Anything you send from your wallet is relayed through the predicate, and anything you receive from elsewhere you receive through predicate.
The predicate is audited for secure. And your wallet address is still your wallet address. You will just see the predicate address on Fuel.
2. Funds Display in Wallets
For some Ethereum VM (EVM) or Solana VM (SVM) wallets, funds will not display in your wallet directly, unless you are interacting with Fuel native applications.
If you see this, don’t panic! Your funds are there and you can move them by following the following steps.
Step 1: Go to Fuel Explorer

Step 2: Connect your wallet

Step 3: Connect a non-native wallet

Step 4: Open drop-down and select “My Account”

Step 5: View your account and assets

Note: This will improve over time as connections between the Fuel Network and wallets are built into the wallets themselves.
3. Blind Signing
Wallets that are built for other chains (EVM or SVM, for example) will use a technique called "Blind signing" to sign transactions. There is some risk to this, and you should only interact with trusted dApps using your non-native wallet.
Conclusion
You can connect essentially any EVM or SVM wallet to Fuel via Fuel Connectors, and you can use them on the network securely. Just be aware of the above experiences as you do so.
Other Options
If you are uncomfortable with any of these experiences or risks, you can use a Fuel Native wallet. A list of trusted Fuel Native wallets is available here.
Predicate Limitations
Given that all operations are done off-chain, it's clear there are inherent limitations to the capabilities of a predicate.
| Predicates | Contracts | |
|---|---|---|
| Access data on chain | ❌ | ✅ |
| Read data from smart contracts | ❌ | ✅ |
| Check date or time | ❌ | ✅ |
| Read block hash or number | ❌ | ✅ |
| Read input coins | ✅ | ✅ |
| Read output coins | ✅ | ✅ |
| Read transaction scripts | ✅ | ✅ |
| Read transaction bytecode | ✅ | ✅ |
These constraints mean that our MultiSig cannot "approve" interactions with contracts. However, it retains the ability to oversee the flow of funds when the correct wallets are involved.
Now that we have a basic understanding of what a predicate is, we can start writing our MultiSig.
Predicate Root
Let's pause here for a moment and build the predicate at the root of the predicate folder.
<TestAction id="build-predicate" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate/predicate' }} />
forc build
Unlike building a contract, constructing the predicate generates an additional piece of information: an address that is hashed from the predicate code of your templated project, known as the predicate root. Since this process is cryptographic, any changes to the code will result in a change in the predicate root.
Since everyone is starting with the exact same templated code, the predicate root should be exactly this:
0x68fec7a57e48f4ec6467d7e09c27272bd8ca72b312ea553a470b98731475ccf3
Looking at the predicate, you can immediately notice several differences. There is no ABI or implementation, but simply a main function that returns true or false.
Notice that we have not "deployed" anything on the Fuel blockchain, yet we already have an address that we can interact with. It is important to remember this:
Predicates are created, not deployed.
Prerequisites
Installation
Already have fuelup installed?
Fuel Wallet
Additionally for this guide, ensure you're using Node.js/npm version {props.nodeVersion}. You can check your Node.js version with:
node -v
Project Setup
Start with a Fuel template and name it sway-store.
<TestAction id="create-project-folder" action={{ name: 'runCommand', commandFolder: 'guides-testing' }} />
pnpm create fuels --pnpm sway-store
Go into the sway-store folder:
cd sway-store
There should already be a folder called sway-programs inside, where your Sway programs will live. Ignore the other programs as we will only focus on the contract program type in this tutorial. Move into your contract folder:
cd sway-programs/contract
Open up the contract folder in VSCode, and inside the src folder you should see a file called main.sw. This is where you will write your Sway contract.
Since we're creating a brand new contract you can delete everything in this file except for the contract keyword.
<TestAction id="sway-program-type" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
The first line of the file is specifically reserved to inform the compiler whether we are writing a contract, script, predicate, or library. To designate the file as a contract, use the contract keyword.
Prerequisites
Installation
Already have fuelup installed?
Project Setup
Start with a new empty folder and name it multisig-predicate.
<TestAction id="create-project-folder" action={{ name: 'runCommand', commandFolder: 'guides-testing' }} />
mkdir multisig-predicate
Go into the multisig-predicate folder:
cd multisig-predicate
Within your terminal start by creating a new sway project called predicate:
<TestAction id="create-predicate" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate' }} />
forc new --predicate predicate
Tip: Notice the
--predicateflag, which tellsforcthat you want to create a project based on a predicate, rather than the default contract program type.
Your project structure generated from the forc command should like this:
<TestAction id="predicate-tree" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate' }} />
tree predicate
predicate
├── Forc.toml
└── src
└── main.sw
1 directory, 2 files
Move into your predicate folder:
cd predicate
In VSCode, navigate to the src folder within the predicate folder, where you will find a file named main.sw. This is the file where your Sway predicate will be written.
Testing the contract
Generating a Test Template in Rust
To create your own test template using Rust, follow these steps with cargo-generate in the contract project directory:
- Install
cargo-generate:
cargo install cargo-generate --locked
{/markdownlint-disable/} 2. Generate the template: {/markdownlint-disable/}
<TestAction id="cargo-generate-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/sway-programs/contract' }} />
cargo generate --init fuellabs/sway templates/sway-test-rs --name sway-store
{/markdownlint-disable/} 3. Update the Cargo.toml file {/markdownlint-disable/}
<TestAction id="temp-update-cargo-toml-file" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/sway-programs/contract/Cargo.toml' }} />
Imports
We will be changing the existing harness.rs test file that has been generated. Firstly we need to change the imports. By importing the Fuel Rust SDK you will get majority of the functionalities housed within the prelude.
<TestAction id="harness-import" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Always compile your contracts after making any changes. This ensures you're working with the most recent contract-abi that gets generated.
Update your contract name and ABI path in the abigen macro to match the name of your contract:
<TestAction id="harness-abi" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Initializing Functions
When writing tests for Sway, two crucial objects are required: the contract instance and the wallets that interact with it. This helper function ensures a fresh start for every new test case so copy this into your test file. It will export the deployed contracts, the contract ID, and all the generated wallets for this purpose.
Replace the get_contract_instance function in your test harness with the function below:
<TestAction id="harness-instance" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Test Cases
Given the immutable nature of smart contracts, it's important to cover all potential edge cases in your tests.
Let's delete the example can_get_contract_id test case and start writing some test cases at the bottom of our harness.rs file.
Setting Owner
For this test case, we use the contract instance and use the SDK's .with_account() method. This lets us impersonate the first wallet. To check if the owner has been set correctly, we can see if the address given by the contract matches wallet 1's address. If you want to dig deeper, looking into the contract storage will show if wallet 1's address is stored properly.
<TestAction id="harness-test-set-owner" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Setting Owner Once
An edge case we need to be vigilant about is an attempt to set the owner twice. We certainly don't want unauthorized ownership transfer of our contract! To address this, we've included the following line in our Sway contract: require(owner.is_none(), "owner already initialized");
This ensures the owner can only be set when it hasn't been previously established. To test this, we create a new contract instance: initially, we set the owner using wallet 1. Any subsequent attempt to set the owner with wallet 2 should be unsuccessful.
<TestAction id="harness-test-set-owner-once" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Buying and Selling in the Marketplace
It's essential to test the basic functionalities of a smart contract to ensure its proper operation. For this test, we have two wallets set up:
- The first wallet initiates a transaction to list an item for sale. This is done by calling the
.list_item()method, specifying both the price and details of the item they're selling. - The second wallet proceeds to purchase the listed item using the
.buy_item()method, providing the index of the item they intend to buy.
Following these transactions, we'll assess the balances of both wallets to confirm the successful execution of the transactions.
<TestAction id="harness-test-buy-sell" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Withdraw Owner Fees
Most importantly, as the creator of the marketplace, you need to ensure you're compensated. Similar to the previous tests, we'll invoke the relevant functions to make an exchange. This time, we'll verify if you can extract the difference in funds.
<TestAction id="harness-test-owner-withdraw" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/tests/harness.rs' }} />
Checkpoint
If you have followed the previous steps correctly your harness.rs test file should look like this:
Running the Tests
{/markdownlint-disable/} Update the shared fuel-toolchain.toml file {/markdownlint-disable/}
<TestAction id="temp-update-fuel-toolchain-toml-file" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/fuel-toolchain.toml' }} />
To run the test located in tests/harness.rs, run the command below inside your contract folder:
<TestAction id="cargo-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/sway-programs/contract' }} />
cargo test
If you want to print outputs to the console during tests, use the nocapture flag:
cargo test -- --nocapture
Now that we're confident in the functionality of our smart contract, it's time to build a frontend. This will allow users to seamlessly interact with our new marketplace!
Testing the predicate
Let's jump back into our MultiSig project again!
cd ../../multisig-predicate/predicate
Generating a Test Template in Rust
Again follow these steps with cargo-generate in the predicate project directory like we did previously:
- Install
cargo-generate:
cargo install cargo-generate --locked
{/markdownlint-disable/} 2. Generate the template: {/markdownlint-disable/}
<TestAction id="cargo-generate-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate/predicate/' }} />
cargo generate --init fuellabs/sway templates/sway-test-rs --name sway-store
{/markdownlint-disable/} 3. Update the Cargo.toml file {/markdownlint-disable/}
<TestAction id="temp-update-cargo-toml-file" action={{ name: 'writeToFile', filepath: 'guides-testing/multisig-predicate/predicate/Cargo.toml' }} />
Imports
Delete the templated code and copy the following imports into your harness file. It's important to pay attention to two main imports: predicates, for obvious reasons, and the ScriptTransactionBuilder, which we'll use to create transactions. These transactions must be signed before being broadcasted to our local network.
<TestAction id="multisig-predicate-test-imports" action={{ name: 'writeToFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
Similar to Rust testing for contracts, we'll import the predicate ABI (Application Binary Interface) to interact with it. Ensure the name of your predicate matches the one you're working with.
<TestAction id="multisig-predicate-test-abi" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
Setup
If you're familiar with Rust testing for Sway projects, much of the setup will be similar. Copy and paste the setup_wallets_and_network function into your harness file.
<TestAction id="multisig-predicate-test-setup" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
The three key setup steps include:
- Configuring the wallets that will act as owners of our multisig, through the configurables you'll see later in the tests.
{/markdownlint-disable/} 2. Setting up the default token (zeroth address) and loading some tokens into each wallet. {/markdownlint-disable/}
{/markdownlint-disable/} 3. Preparing the network to broadcast our transaction, enabling us to successfully unlock the tokens from the predicate later. {/markdownlint-disable/}
Since the predicate address is deterministic, we don't need to copy it as we do with smart contracts, which are deployed with a different address each time. We can leverage SDKs to build the predicate, ensuring we're working with the correct address without error!
{/markdownlint-disable/} 4. Gas isn't just used by the script itself; you also pay for the size of the transaction, signature checks, VM initialization, etc. These costs do not count towards the script gas so it might be hidden. {/markdownlint-disable/}
Test Cases
Valid 2 of 3 signatures
Now, let's review the sequence of actions we'll take to simulate a real-world scenario, copy and paste the first test below and let's break it down step by step:
<TestAction id="multisig-predicate-test-valid-two-of-three" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
- A group or individuals create their multisig by specifying the wallets that will safeguard the funds.
- Funding the predicate.
- Extracting the tokens when needed by building a transaction and getting the original wallets to sign it.
- Broadcasting the transaction to unlock the funds from the predicate.
For step 1, as mentioned earlier, when we configure the number of required signatures (up to 3) and the 3 addresses that will safeguard our funds. Importing the ABI will automatically load a PredicateNameConfigurable type. In our case, that will be MultiSigConfigurables. There will be a corresponding with_configurable function to help you load each configurable. In our case, with_REQUIRED_SIGNATURES and with_SIGNERS are both loaded in!
How convenient!
Next, we'll load our original predicate binary with our new configurables to generate our personalized predicate instance. Simply input your configurables using the with_configurables function, and this will give us a unique predicate root based on our inputs.
For step 2, transferring funds to our newly generated predicate root is as straightforward as any other blockchain transfer.
In step 3, when the multisig holders decide to use the locked funds, we build a transaction specifying the inputs and outputs. Pay close attention to the outputs; we need to specify where the tokens from the predicate are going, which native asset they involve, and the amount. We're essentially extracting a portion of the original base asset sent into the predicate.
The correct wallet addresses configured in the configurables must sign the transactions. This information, loaded as witness data, will evaluate our predicate to true. It's crucial to provide enough correct, unique signatures; otherwise, the transaction will fail, as demonstrated in later tests. Since our test only requires 2 signatures, we need to provide just those.
After the evaluation is correctly done, all we need to do is broadcast the transaction, and the requested funds should return to wallet 1.
Valid unordered 3 of 3 signatures
The setup for the second test, multisig_mixed_three_of_three, follows the same scheme, showcasing that the transaction signing can be done in any order by valid wallets.
<TestAction id="multisig-predicate-test-valid-3-of-3" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
Insufficient valid Signatures
The same principle applies to the third test, multisig_not_enough_signatures_fails, where the transaction will fail if there aren't enough signatures.
<TestAction id="multisig-predicate-test-insufficient-1-of-3" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/tests/harness.rs' }} />
Checkpoint
If you have followed the previous steps correctly, your harness.rs test file should look like this:
Running the Tests
To run the test located in tests/harness.rs, use:
<TestAction id="cargo-test" action={{ name: 'runCommand', commandFolder: 'guides-testing/multisig-predicate/predicate/' }} />
cargo test
If you want to print outputs to the console during tests, use the nocapture flag:
cargo test -- --nocapture
Congratulations on making it this far! We've confirmed that our Multisig works.
Predicates aren't meant to be intimidating. State-minimized DeFi applications should be the standard, rather than resorting to gas golfing or writing assembly code for these optimizations. Now that you have predicates in your toolbox, go out and explore what other state-minimized DeFi applications you can build!
Signature Verification
Let's define a helper function called verify_signatures() that checks the validity of each signature provided and rejects any that are invalid, ensuring all signatures are unique.
Copy the signature verification helper function in your main.sw below:
<TestAction id="sway-function" action={{ name: 'modifyFile', filepath: 'guides-testing/multisig-predicate/predicate/src/main.sw' }} />
As mentioned earlier, we will utilize the transaction witnesses and the transaction hash to verify each signature, matching them to the wallets that were previously configured.
-
Parameter
i: This parameter represents the index of a signer in a predefined list of signers defined in the configurable. It's used to identify which signer's signature the function is currently attempting to verify. -
Signature Verification Loop: The function then enters a loop, iterating up to three times. This loop represents an attempt to verify the signature against up to three pieces of witness data (signatures) attached to the transaction.
-
Signature Recovery: Inside the loop, for each iteration defined by
j, it retrieves the current signature (current_signature) from the transaction's witness data usingtx_witness_data::<B512>(j). It then attempts to recover the address that generated this signature (current_address) by using theec_recover_addressfunction, which takes the current signature and the transaction hash as inputs. -
Address Matching: After recovering the address, the function checks if this address matches the address of the
ith signer in theSIGNERSlist. If a match is found, it means the signature from one of the signers has been successfully verified, and the function returns1.
-
{/markdownlint-disable/}
3. Return Value: The function returns 1 if a matching signature is found for the ith signer, indicating successful verification. If no matching signature is found after checking up to three signatures, the function returns 0, indicating that the signature for the ith signer could not be verified.
{/markdownlint-disable/}
This allows for flexible signature verification, accommodating scenarios where signatures from the required signers can be presented in any order and ensuring that each signature is uniquely accounted for without allowing duplicates from the same wallet.
Defining The Storage Block
Next, we'll introduce the storage block. This is where you store all persistent state variables in your contract.
Variables declared within a function and not saved in the storage block will be discarded once the function completes its execution. Add the storage block below to your main.sw file:
<TestAction id="sway-storage" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
The first variable we've stored is item_counter, a number initialized to 0. This counter can be used to track the total number of items listed.
StorageMap
A StorageMap is a unique type that permits the saving of key-value pairs within a storage block.
To define a storage map, you need to specify the types for both the key and the value. For instance, in the example below, the key type is u64, and the value type is an Item struct.
Here, we are creating a mapping from the item's ID to the Item struct. Using this, we can retrieve information about an item using its ID.
Options
Here, we are defining the owner variable as one that can either be None or hold an Identity.
If you want a value to be potentially null or undefined under specific conditions, you can employ the Option type. It's an enum that can take on either Some(value) or None. The keyword None indicates the absence of a value, while Some signifies the presence of a stored value.
Defining an Item Struct
Struct is short for structure, which is a data structure similar to an object in JavaScript. You define a struct with the struct keyword in Sway and define the fields of a struct inside curly brackets.
The core of our program is the ability to list, sell, and get items.
Let's define the Item type as shown below to write into your main.sw file:
<TestAction id="sway-struct" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/sway-programs/contract/src/main.sw' }} />
The item struct will contain an ID, price, the owner's identity, a string representing a URL or identifier for off-chain data about the item (such as its description and photos), and a "total bought" counter to track the overall number of purchases.
Types
The Item struct uses three types: u64, str, and Identity.
u64: a 64-bit unsigned integer.
In Sway, there are four native types of numbers:
u8: an 8-bit unsigned integer.u16: a 16-bit unsigned integer.u32: a 32-bit unsigned integer.u64: a 64-bit unsigned integer.u256: a 256-bit unsigned integer.
An unsigned integer means there is no + or - sign, making the value always positive. u64 is the default type used for numbers in Sway.
In JavaScript, there are two types of integers: number and BigInt. The primary difference between these types is that BigInt can store much larger values. Similarly, each numeric type in Sway has its maximum value that can be stored.
String Array: a string is a built-in primitive type in Sway. The number inside the square brackets indicates the size of the string.
Identity: an enum type that represents either a user's Address or a ContractId. In Sway, a contract and an EOA (Externally Owned Account) are distinctly differentiated. Both are type-safe wrappers for b256.
Building the Frontend
Generate contract types
In your folder you have a fuels.config.ts file, you can use the fuels build command to rebuild your contract and generate types.
Running this command will interpret the output ABI JSON from your contract and generate the correct TypeScript definitions.
If you see the folder sway-store/counter-contract/out you will be able to see the ABI JSON there.
Inside the sway-store/src directory run:
<TestAction id="typegen" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/' }} />
npx fuels build
A successful process should print and output like the following:
Building..
Building Sway programs using built-in 'forc' binary
Generating types..
🎉 Build completed successfully!
Now you should be able to find a new folder sway-store/src/sway-api.
Wallet Providers
In your main.tsx file, wrap your App component with the FuelProvider and QueryClientProvider components to enable Fuel's custom React hooks for wallet functionalities.
This is where you can pass in custom wallet connectors to customize which wallets your users can use to connect to your app.
<TestAction id="fe-index-all" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/main.tsx' }} />
Connecting to the contract
Next, open the src/App.tsx file, and replace the boilerplate code with the template below:
<TestAction id="fe-app-template" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/App.tsx' }} />
At the top of the file, change the CONTRACT_ID to the contract ID that you deployed earlier and set as a constant.
React hooks from the @fuels/react package are used in order to connect our wallet to the dapp. In the App function, we can call these hooks like this:
The wallet variable from the useWallet hook will have the type FuelWalletLocked.
You can think of a locked wallet as a user wallet you can't sign transactions for, and an unlocked wallet as a wallet where you have the private key and are able to sign transactions.
The useMemo hook is used to connect to our contract with the connected wallet.
Styling
Copy and paste the CSS code below in your App.css file to add some simple styling.
<TestAction id="fe-css-template" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/App.css' }} />
UI
In our app we're going to have two tabs: one to see all of the items listed for sale, and one to list a new item for sale.
We use another state variable called active that we can use to toggle between our tabs. We can set the default tab to show all listed items.
Next we can create our components to show and list items.
Listing an Item
Inside components, create a file inside called ListItem.tsx.
<TestAction id="create-list-item-file" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/src/components' }} />
touch ListItem.tsx
At the top of the file, import the useState hook from react, the generated contract ABI from the contracts folder, and bn (big number) type from fuels.
<TestAction id="fe-list-item-import" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
This component will take the contract we made in App.tsx as a prop, so let's create an interface for the component.
<TestAction id="fe-list-item-interface" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
We can set up the template for the function like this.
<TestAction id="fe-list-item-list-item" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
To list an item, we'll create a form where the user can input the metadata string and price for the item they want to list.
Let's start by adding some state variables for the metadata and price. We can also add a status variable to track the submit status.
<TestAction id="fe-list-item-state-variables" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
We need to add the handleSubmit function.
We can use the contract prop to call the list_item function and pass in the price and metadata from the form.
<TestAction id="fe-list-item-handle-submit" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
Under the heading, add the code below for the form:
<TestAction id="fe-list-item-return-form" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ListItem.tsx' }} />
Now, try listing an item to make sure this works.
You should see the message Item successfully listed!.
Show All Items
Next, let's create a new file called AllItems.tsx in the components folder.
<TestAction id="create-all-item-file" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/src/components' }} />
touch AllItems.tsx
Copy and paste the template code below for this component:
<TestAction id="fe-all-item-template" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/components/AllItems.tsx' }} />
Here we can get the item count to see how many items are listed, and then loop through each of them to get the item details.
First, let's create some state variables to store the number of items listed, an array of the item details, and the loading status.
<TestAction id="fe-all-item-state-variables" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/AllItems.tsx' }} />
Next, let's fetch the items in a useEffect hook.
Because these are read-only functions, we can simulate a dry-run of the transaction by using the get method instead of call so the user doesn't have to sign anything.
<TestAction id="fe-all-item-use-effect" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/AllItems.tsx' }} />
If the item count is greater than 0 and we are able to successfully load the items, we can map through them and display an item card.
The item card will show the item details and a buy button to buy that item, so we'll need to pass the contract and the item as props.
<TestAction id="fe-all-item-cards" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/AllItems.tsx' }} />
Item Card
Now let's create the item card component.
Create a new file called ItemCard.tsx in the components folder.
<TestAction id="create-item-card-file" action={{ name: 'runCommand', commandFolder: 'guides-testing/sway-store/src/components/' }} />
touch ItemCard.tsx
After, copy and paste the template code below.
<TestAction id="fe-item-card-template" action={{ name: 'writeToFile', filepath: 'guides-testing/sway-store/src/components/ItemCard.tsx' }} />
Add a status variable to track the status of the buy button.
<TestAction id="fe-item-card-status" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ItemCard.tsx' }} />
Create a new async function called handleBuyItem.
Because this function is payable and transfers coins to the item owner, we'll need to do a couple special things here.
Whenever we call any function that uses the transfer or mint functions in Sway, we have to append the matching number of variable outputs to the call with the txParams method. Because the buy_item function just transfers assets to the item owner, the number of variable outputs is 1.
Next, because this function is payable and the user needs to transfer the price of the item, we'll use the callParams method to forward the amount. With Fuel you can transfer any type of asset, so we need to specify both the amount and the asset ID.
<TestAction id="fe-item-card-buy-item" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ItemCard.tsx' }} />
Then add the item details and status messages to the card.
<TestAction id="fe-item-cards" action={{ name: 'modifyFile', filepath: 'guides-testing/sway-store/src/components/ItemCard.tsx' }} />
Now you should be able to see and buy all of the items listed in your contract.
Checkpoint
Ensure that all your files are correctly configured by examining the code below. If you require additional assistance, refer to the repository here
App.tsx
AllItems.tsx
ItemCard.tsx
ListItem.tsx
Run your project
Inside the fuel-project/frontend directory run:
<TestAction
id="start-app"
action={{
name: 'runCommand',
preCommand: "pnpm pm2 start 'PORT=4000 BROWSER=none
npm start
Compiled successfully!
You can now view frontend in the browser.
Local: http://localhost:3000
On Your Network: http://192.168.4.48:3000
Note that the development build is not optimized.
To create a production build, use npm run build.
And that's it for the frontend! You just created a whole dapp on Fuel!
{/TODO: MAKE THIS MORE RELIABLE/} {/* <TestAction id="wait-after-start-app" action={{ name: 'wait', timeout: 5000 }} />
<TestAction id="go-to-frontend" action={{ name: 'goToUrl', url: "http://localhost:4000" }} />
<TestAction id="click-connect-button" action={{ name: 'clickByRole', role: "button", elementName: "Connect" }} />
<TestAction id="click-fuel-wallet" action={{ name: 'clickByLabel', label: 'Connect to Fuel Wallet' }} />
<TestAction id="approve-connect" action={{ name: 'walletApproveConnect', }} />
<TestAction id="wait-after-connect" action={{ name: 'wait', timeout: 5000 }} />
<TestAction id="click-buy-item-button" action={{ name: 'clickByTestId', testId: "buy-button-1" }} />
<TestAction id="approve-txn" action={{ name: 'walletApprove', }} />
<TestAction id="wait-after-approve" action={{ name: 'wait', timeout: 5000 }} />
<TestAction id="check-text" action={{ name: 'getByLocator-save', locator: "h3 ~ div", }} />
<TestAction id="wait-after-buy" action={{ name: 'wait', timeout: 18000 }} />
<TestAction id="check-if-purchased" action={{ name: 'checkValue', index: 0, value: 'Purchased ✅' }} />*/}
Fuel Glossary
Address
An address is a cryptographic hash representing an identity of a wallet or a predicate root.
AssetId
An asset ID is a unique identifier for an on-chain asset. It is derived from the root of the bytecode of the contract minting the asset.
Base Asset
The base asset is the underlying asset needed to perform any transactions on a blockchain. It is used to pay gas for transactions. On Ethereum, the base asset is ETH.
Block
A block is a record of many transactions, that are grouped together and cryptographically hashed. The linked blocks form a chain, also called a blockchain.
Block Explorer
A block explorer is an interface for block and transaction data produced by a blockchain. You can use a block explorer to explore and verify addresses, contracts, and transaction histories.
Block Height
The block height refers to the total number of valid blocks produced in the history of a blockchain, starting with the genesis block.
Block ID
A block ID is a unique identifier for a particular block.
Bridge
A bridge is a mechanism that allows the transfer of data or assets from one blockchain to another.
Bytecode
Bytecode is machine-readable code, usually generated from a compiler.
Chain
Another name for a blockchain.
ChainId
A unique ID for a blockchain.
Client
The Fuel client refers to the software that runs the Fuel Virtual Machine. It can be used to validate and produce blocks, and send transactions.
Coinbase
Coinbase refers to the validators paying themselves for processing a block from the transaction fees. Having a coinbase transaction on each block makes this process transparent to all users.
Consensus
The consensus layer defines the state and validates that all nodes on the blockchain have the same state.
Consensus Parameters
Consensus parameters are the rules used by clients to determine the validity of and finalize a block.
Contract Call
Calling a contract means invoking a function from a smart contract that has been deployed to the blockchain.
Contract ID
The contract ID is a unique identifier for a contract derived from the root of the contract bytecode.
Data Availability
The data availability layer ensures that block data has been published to the network.
EIP
EIP stands for Ethereum Improvement Proposal. It refers to a proposal to upgrade the core software powering the Ethereum blockchain.
EOA
EOA stands for Externally Owned Account. It refers to a wallet address that is not controlled by a contract.
EVM
EVM stands for Ethereum Virtual Machine, which is the virtual machine used for the Ethereum network.
Execution
Execution refers to the processing of transactions by nodes in a network.
Faucet
A faucet is a service that provides free tokens for a testnet.
Forc
Forc is short for Fuel Orchestrator. Similar to Cargo for Rust, Forc is the build system and package manager for Sway. It is used to build, test, and deploy Sway contracts.
Fraud Proof
Fraud proofs are a blockchain verification mechanism whereby a claim on a new block is accepted unless a proof the claim is invalid is provided within some configurable time window. Both the Fuel protocol and the FuelVM are designed to be fraud-provable in restrictive environments such as the Ethereum Virtual Machine.
Fuel
The Fuel blockchain.
Fuels
Fuels is the name of the Fuel Rust and Typescript SDKs used to interact with a contract, similar to ethers.js or web3.js
Fuelup
Fuelup is the official toolchain and package manager for the Fuel toolchain.
FuelVM
The FuelVM is the virtual machine powering the Fuel blockchain.
Fuel Core
fuel-core is the name of the Fuel client implementation.
Gas
Gas is a variable fee charged by a node to process a transaction that is executed on-chain.
Indexer
An indexer is a program that watches and organizes blockchain data so it can be easily queried.
Input
An input refers to a transaction input, which is a UTXO consumed by a transaction.
Layer 1 (L1)
Also called a level 1, this refers to a base layer blockchain that is not built on top of any other blockchain.
Layer 2 (L2)
Also called a level 2, this is a blockchain that is built on top of another blockchain. Layer 2 networks can offer unique benefits like allowing for cheaper transactions or sovereign rollups that can fork without forking the base layer.
Light Client
A light client is a client that doesn't validate blocks and transactions but still offers some functionality to send transactions.
Locked Wallet
A locked wallet is a wallet that can only interact with read-only smart contract methods.
Mainnet
Mainnet refers to the main network of a blockchain, as opposed to a testnet.
Merkle Tree
A Merkle tree is a data structure which uses a cryptographic hash function recursively to condense a set of data into a single value, called the root. It allows efficient proofs that a given element is part of the set.
Message
A type of input that only includes specific metadata, and is often used for bridging.
Mint
Minting refers to the creation of new coins.
Modular
Referring to a blockchain architecture that allows for execution, settlement, consensus, and data availability to run on separate layers.
Monolithic
Referring to a blockchain architecture that handles execution, settlement, consensus, and data availability all at the same time on a single layer.
Native Asset
With Fuel any contract can make its own native asset. On Ethereum, the only native asset is ETH. This allows for much cheaper token transactions because it doesn't require any contract state changes. It also allows you to directly forward any asset in a transaction call, avoiding the need for the approve and transferFrom mechanisms.
Network
Another name for a blockchain.
Node
A client that validates and produces blocks for the network.
Optimistic Rollup
An optimistic rollup is a sidechain that uses fraud proofs to verify transactions instead of relying on a majority of validators to be honest.
Output
An output refers to a transaction output, or which UTXOs are output by a transaction.
Parallel Transactions
Parallel transactions refers to the ability of the FuelVM to process multiple transactions in parallel.
Predicate
A predicate is a pure function that can return true or false, and is sent inside a transaction as bytecode and checked at transaction validity time. If it evaluates to false the transaction will not be processed, and no gas will be used. If it evaluates to true, any coins belonging to the address equal to the Merkle root of the predicate bytecode may be spent by the transaction.
Private Key
A cryptographic key that is used to prove ownership by producing a digital signature. It should be kept private (or secret) as it can grant access to a wallet.
Public Key
A cryptographic key that is generated from its associated private key and can be shared publicly. Addresses are derived from public keys.
Receipt
A receipt is a data object that is emitted during a transaction and contains information about that transaction.
Reentrancy attack
A type of attack in which the attacker is able to recursively call a contract function so that the function is exited before it is fully executed. This can result in the attacker being able to withdraw more funds than intended from a contract.
Rollup
A rollup is a scaling solution for layer 1 blockchains that "rolls up" or batches transactions as calldata.
Script
A script is runnable bytecode that executes once on-chain to perform some task. It does not represent ownership of any resources and it cannot be called by a contract. A script can return a single value of any type.
Settlement
Settlement refers to how and where on-chain disputes are resolved or settled.
Sidechain
A sidechain is a blockchain that runs independently but is connected to another blockchain (often Ethereum Mainnet) by a two-way bridge.
Signature
A cryptographic signature from a wallet, usually in reference to a signature for a message.
Smart Contract
Also referred to as a contract, a smart contract is a set of programming functions with persistent state that is deployed on a blockchain. Once deployed, the contract code can never be changed or deleted, and anyone can access public functions without permission.
State Channel
State channels allow for users to conduct any number of off-chain transactions while only submitting two on-chain transactions to open and close the channel. This reduces the number of on-chain transactions needed, which reduces the cost and saves time.
Sway
Sway is the official programming language for Fuel. It is a domain-specific language crafted for the FuelVM and inspired by Rust.
Testnet
Testnet is short for test network. You can use a testnet to deploy and test contracts for free.
Toolchain
A toolchain is a set of related tools. The Fuel toolchain includes fuelup, forc, fuel-core, and fuels.
Transaction
A transaction is any interaction with the blockchain, for example sending coins from one address to another.
Unlocked Wallet
An unlocked wallet can interact with both read and write smart contract methods.
UTXO
UTXO stands for unspent transaction output.
UTXO Model
A UTXO model is a blockchain model that doesn't keep track of account balances. Instead, it uses inputs and outputs to manage state, which allows for fast parallel transactions.
Validator
A validator refers to a network validator or node. Validators help validate transactions and produce blocks.
Witness
A witness refers to the cryptographic signatures from actors involved in authorizing a transaction, including the transaction signers, contract callers, and block producers.
Zero-Knowledge Proof
A method that allows for the verification of secret information without revealing the secret.
Zero-Knowledge Rollup
A rollup that uses zero-knowledge proofs to verify transactions. In ZK rollups, each rollup block posted to the contract must be accompanied by a validity proof (a succinct proof that the block is valid), which is also verified by the contract. Blocks are thus finalized immediately, and withdrawals can be processed in the same Ethereum block.
Quickstart
Get started with Fuel and discover the path that best suits your needs.
What is Fuel?
Fuel is an operating system purpose built for Ethereum Rollups. Fuel allows rollups to solve for PSI (parallelization, state minimized execution, interoperability) without making any sacrifices.
Here is how we do it:
FuelVM
The FuelVM learns from the Ethereum ecosystem. It implements improvements suggested to the Ethereum VM (EVM) for many years that couldn’t be implemented due to the need to maintain backward compatibility, including parallel transaction execution and multiple native assets.
Fuel delivers unmatched processing capacity through its ability to execute transactions in parallel by using strict state access lists in the form of a UTXO model. With the FuelVM, Fuel full nodes identify the accounts a transaction touches, mapping out dependencies before execution. This enables Fuel to use far more threads and cores of your CPU that are typically idle in single-threaded blockchains. As a result, Fuel can deliver far more compute, state accesses, and transactional throughput than its single-threaded counterparts.
Sway Language
Fuel provides a powerful and sleek developer experience with our own domain-specific language (DSL) called Sway. Sway is based on Rust and includes syntax to leverage a blockchain VM without needlessly verbose boilerplate. Sway was created alongside the FuelVM and designed for the high-compute Fuel environment.
Rust + Solidity = Sway
Sway prioritizes compile-time analysis and safety, similar to Rust’s borrow checker and safety-first semantics. Additionally, it has the syntax of Rust. From Solidity, Sway took the notion of a smart-contract-paradigm language with built-in top-level contract storage and blockchain mechanisms for ergonomic and safe contract programming.
Sway brings the notion of static auditing to smart contracts. In addition, Sway is highly performant and has extensible optimization passes and a modular backend for targeting different blockchain architectures.
<CardSection versionSet={props.versionSet} cardsInfo={[ { link: '/docs/sway', nightlyLink: '/docs/nightly/sway', isExternal: false, heading: 'Sway', headingIcon: 'Code', body: 'Read the official Sway documentation.', }, { link: 'https://sway-playground.org/', isExternal: true, heading: 'Sway Playground', headingIcon: 'Browser', body: 'Get started experimenting with Sway in the browser.', }, { link: 'https://swaybyexample.com/', isExternal: true, heading: 'Sway By Example', headingIcon: 'Beach', body: 'An introduction to Sway with bite-sized simple examples', }, { link: 'https://github.com/FuelLabs/sway-examples', isExternal: true, heading: 'Sway Examples', headingIcon: 'Robot', body: 'Examples of full-stack DeFi applications', }, { link: '/docs/sway-libs', nightlyLink: '/docs/nightly/sway-libs', isExternal: false, heading: 'Sway Libraries', headingIcon: 'Book', body: 'Find useful libraries written in Sway.', }, { link: '/docs/sway-standards', nightlyLink: '/docs/nightly/sway-standards', isExternal: false, heading: 'Sway Standards', headingIcon: 'Book', body: 'Learn about standards for the Sway language', }, { link: 'https://fuellabs.github.io/sway/master/std/', isExternal: true, heading: 'std-lib Reference', headingIcon: 'Book', body: 'Find definitions for helpful types and methods in Sway.', }, { link: 'https://github.com/FuelLabs/sway-applications', isExternal: true, heading: 'Example Applications', headingIcon: 'Apps', body: 'Explore end-to-end applications written in Sway.', }, ]} />
Developer Tooling
Part of what makes Sway so powerful is the fantastic suite of developer tools surrounding it. The Fuel development environment retains the benefits of smart contract languages like Solidity, while adopting the paradigms introduced in the Rust tooling ecosystem.
Now, developers can have a completely vertically integrated experience where every component, from the virtual machine to the CLI, works in harmony.
Sway Tooling
<CardSection versionSet={props.versionSet} cardsInfo={[ { link: '/docs/forc', nightlyLink:'/docs/nightly/forc', isExternal: false, heading: 'Forc', headingIcon: 'Tool', body: 'Explore the Fuel Orchestrator that helps you build, test, and deploy your Sway projects.', }, { link: '/guides/installation/', nightlyLink: '/guides/installation/', isExternal: false, heading: 'Fuelup', headingIcon: 'Settings', body: 'Learn more about the official Fuel toolchain manager that helps install and manage versions.', } ]} />
SDKs & API
<CardSection versionSet={props.versionSet} cardsInfo={[ { link: '/docs/fuels-rs', nightlyLink: '/docs/nightly/fuels-rs', isExternal: false, heading: 'Rust SDK', headingIcon: 'BrandRust', body: 'Test and interact with your Sway program in Rust.', }, { link: '/docs/fuels-ts', nightlyLink: '/docs/nightly/fuels-ts', isExternal: false, heading: 'Typescript SDK', headingIcon: 'BrandTypescript', body: 'Test and interact with your Sway program in TypeScript.', }, { link: '/docs/wallet', nightlyLink: '/docs/nightly/wallet', isExternal: false, heading: 'Wallet SDK', headingIcon: 'Wallet', body: 'Seamlessly integrate a wallet into your application.', }, { link: '/docs/graphql', nightlyLink: '/docs/nightly/graphql', isExternal: false, heading: 'GraphQL API', headingIcon: 'ChartDots3', body: 'Learn about the GraphQL API and interact with the Fuel Network.', }, ]} />
Network
<CardSection
versionSet={props.versionSet}
cardsInfo={[
{
link: /docs/specs,
nightlyLink: /docs/nightly/specs,
isExternal: false,
heading: 'Specs',
headingIcon: 'ListDetails',
body: 'Explore the specifications for the Fuel Network.',
},
{
link: props.explorerUrl,
isExternal: true,
heading: 'Explorer',
headingIcon: 'Search',
body: 'Explore transactions on the Fuel network.',
},
{
link: props.bridgeUrl,
isExternal: true,
heading: 'Bridge',
headingIcon: 'BuildingBridge',
body: 'Bridge assets to the Fuel network.',
},
{
link: props.faucetUrl,
isExternal: true,
heading: 'Faucet',
headingIcon: 'Coin',
body: Get ${props.fuelTestnet} testnet tokens.,
}
]}
/>
Contributing
🌟 Welcome, and thank you for considering contributing to the Fuel docs! 🌟
Before you get started, please take a moment to read through this contributing guide. It contains important information and helpful tips for contributing to the Fuel documentation and guides.
Documentation Content
All documentation, with the exception of the guides and intro section, is pulled in to the docs-hub repo via git submodules.
All contributions to the content of the documentation should be done in the original repository. Below, you will find a list indicating the locations of the source documentation for each book:
Style Guide
See the Style Guide for guidance on how to edit or write documentation.
Docs-related GitHub workflows
There are several GitHub actions that run against the docs in each repo to help catch issues. You can learn more about how each of these work in the github-actions repo.
General
You can find the repository for this website on GitHub. For instructions on how to run the repository locally, see the README.
Contribution flow
This is a rough outline of what a contributor's workflow looks like:
- Create a feature branch off of the master branch, which is typically the base for your work.
- Make your changes, and commit your work.
- Run tests and make sure all tests pass.
- Push your changes to a branch in your fork of the repository and submit a pull request.
- Use one of the following tags in the title of your PR:
feat:- A new featurefix:- A bug fixdocs:- Documentation only changesstyle:- Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)refactor:- A code change that neither fixes a bug nor adds a featureperf:- A code change that improves performancetest:- Adding missing tests or correcting existing testsbuild:- Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)ci:- Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)chore:- Other changes that don't modifysrcor test filesrevert:- Reverts a previous commit
- Use one of the following tags in the title of your PR:
- Complete the contributor agreement on the PR if it is not already completed.
- Your PR will be reviewed and some changes may be requested.
- Once you've made the requested changes, your PR must be re-reviewed and approved.
- If the PR becomes out of date, you can use GitHub's 'update branch' button.
- If there are conflicts, you can merge and resolve them locally. Then push to your PR branch. Any changes to the branch will require a re-review.
- GitHub Actions will automatically test all authorized pull requests.
- Use GitHub to merge the PR once approved.
Linking issues
If the pull request resolves the relevant issues, and you want GitHub to close these issues automatically after it merged into the default branch, you can use the syntax (KEYWORD #ISSUE-NUMBER) like this:
close #123
If the pull request links an issue but does not close it, you can use the keyword ref like this:
ref #456
Multiple issues should use full syntax for each issue and separate by a comma, like:
close #123, ref #456
Reporting Bugs
If you notice any bugs in the live website, please create a new issue on GitHub with:
- a description of the bug
- step-by-step instructions for how to reproduce the bug
Understanding the GitHub Workflows
The docs-hub repo uses GitHub Actions to automate the process of updating the documentation, checking for broken links, and more. This page explains the different workflows and how to fix them if they fail.
DocSearch Scrap (docs-scrapper.yml )
This action updates the Algolia search index by scraping the live docs.fuel.network site. It only runs when a Fuel contributor manually runs it.
Guides (guides.yml )
This action runs a spell check on all the guides to catch any mispelled words. It also runs Playwright tests for some of the guides to make sure they work as expected.
The files checked for spelling are configured in .spellcheck.yml. This is also where you can configure what types of elements are ignored.
If the spell check test fails:
-
look up the word in the question to verify it is a real word and is correctly spelled
-
If it is a file name or is code, use backticks to ignore the word.
-
If it is a real word that is spelled correctly, or an acronym that is either common or is defined already, add it to
spell-check-custom-words.txt. -
If possible, rewrite the sentence.
-
If it otherwise should be ignored, you can configure the pipeline in
.spellcheck.yml.
To fix a failed guides test, refer to the Guides Testing section.
Links (links.yml )
This workflow tests the links in the docs-hub to make sure none are broken.
PR Checks (pr.yml )
This workflow checks the name of the pull request, checks to make sure there are no dependency vulnerabilities, and runs a lint check for the markdown files and code.
To fix a failing PR title check, change the name of your PR so it uses the convention below:
Available types:
- feat: A new feature
- fix: A bug fix
- docs: Documentation only changes
- style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
- refactor: A code change that neither fixes a bug nor adds a feature
- perf: A code change that improves performance
- test: Adding missing tests or correcting existing tests
- build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
- ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
- chore: Other changes that don't modify src or test files
- revert: Reverts a previous commit
To fix a failing audit check:
pnpm audit --fix
pnpm install
To fix a failing lint check:
pnpm lint:fix:all
Update Nightly (update-nightly.yml )
This workflow runs every day Monday through Friday to update the nightly version of the documentation. See the Versions section for more information.
Guides
All guides can be found in the docs/guides folder of the docs-hub repo.
The guide content is in the docs/guides/docs folder while the example code is found in the docs/guides/examples folder.
Note that the some content is pulled in from submodules. To make any changes to the content or code from a submodule, you must make a pull request on the source repo.
Style Guide
See the Style Guide for guidance on how to edit or write guides.
Testing
Some guides are tested with a GitHub workflow that runs on pull requests.
If you are creating a new guide, it is recommended you create a test using the TestAction component.
You can run the tests locally with the command below:
pnpm test:guides
Here is how it works:
Within the guide markdown files, there are TestAction components that are used run a Playwright test. The test follows each step in the guide based on these components and checks to see if everything works as expected. You can find the test files inside the tests folder.
The TestAction component accepts two props:
The id must be a unique string id.
The action prop contains information about the action to run the in test.
You can find examples for how to use this component in the docs/guides/docs folder, you can find all of the action options in tests/utils/types.ts.
Refer to the Playwright docs for information on locators and selecting elements in a test.
Style Guide
This style guide is a set of guidelines for writing and editing documentation. It's important to follow these guidelines to ensure that our documentation is consistent and easy to read.
General Guidelines
Writing
- Use a friendly and conversational tone of voice.
- Use an active voice (vs. a passive voice).
- Avoid long paragraphs or sentences.
- Always use accurate and verified information.
- Maintain consistency in style across pages and sections.
- Assume the reader does not have a lot of context. Keep in mind that readers have different levels of expertise.
- Don't use click here or read this document for links. Just link the thing in context.
- Don’t use double negatives.
Words
- Use second-person perspective (use you).
- Use American English.
- Use simple (but accurate) words.
- Avoid slang, jargon, or making up new words. Everything should be easy to translate into major languages.
- Avoid gendered words or pronouns like “his”, “her”, “manned”, etc.
- Define acronyms and abbreviations on first usage and if they're used infrequently.
- Use italics or bold text to emphasize a word. Avoid using all capital letters.
- Avoid using words that indicate time like “new feature”, as it may fall out-of-date quickly.
- Avoid the word “please” in an instruction.
- Avoid violent words.
- Don’t use offensive language.
Code
- Use code examples whenever possible.
- Always specify the language of a code block.
- Avoid hard-coded examples, and instead import code examples from code that is routinely tested.
- Use comments to define code examples to be imported instead of line numbers that may change.
- Always wrap inline code in backticks.
- Always use code fences when showing commands and separate commands from console outputs. The user should be able to copy and paste the entire code in the code fence.
- Use descriptive variable names in code examples. Don’t use
foo,bar,baz, etc.
Organizing Information
- Use the standard HTML heading hierarchy: The first line should be an
h1(use 1 # in markdown) and should be the onlyh1on the page. The subheadings shouldn’t skip a level, e.g.h3tags should only be insideh2tags. - Organize information so that readers can skim the page and get an answer for the most common questions quickly. Use subheadings to call out important information, and use a blockquote to identify supplemental information.
- Avoid using tables.
Graphics
- Don’t create complex flow-charts (having more than 5-6 items).
- Don't use images of text or code. Use the actual text or code in markdown format.
Lists
- Use numbers, number-letter combinations (1.a, etc.), or bullet points for lists. Do not use Roman numerals or letters alone.
Guides
If you are writing for a guide or the intro section, follow these additional guidelines:
Components
To maintain accuracy and consistency, it is recommended to use the available React components within a guide whenever they apply. For example:
- Use the
CodeImportandTextImportcomponents instead of copying and pasting code or text. - For images, use the
Box.Centeredcomponent to center the image. - For content only applicable to a certain version of the docs, use the
ConditionalContentcomponent.
You can find examples for how to these components within the docs/guides/docs folder.
For a full list of components available, see the src/components/MDXRender.tsx component.
Variables
There are several variables passed into the MDX context that you can use within a guide. You can find a full list in the src/components/MDXRender.tsx component.
You can then use these variables within a guide like so:
The faucet URL is {props.faucetUrl}
Which would render as: The faucet URL is {props.faucetUrl}
Versions
There are two version sets of the docs available in the docs-hub: testnet(the default version), and nightly.
- The default version set is compatible with the
latesttoolchain and testnet. - The
nightlyversion set reflects the latest releases on GitHub. These versions may not be compatible with each other.
Updating the Nightly Versions
There is a Github action that runs every Monday and Thursday at 12:00 UTC to update the nightly versions of the docs.
You can also update the nightly versions locally by running the command below:
pnpm docs:update:nightly
The nightly versions should be kept at the latest release on GitHub for each tool.
Updating the Default Versions
To change the default versions, update the src/config/versions.json file and run this command:
pnpm docs:update
This command will both update the the default versions to match the configuration file and make sure the nightly versions are updated.
Here is how to decide what default versions to use:
- The Sway &
forcversions should match what is on thelatesttoolchain. - The version of the wallet SDK should match the version of the Fuel Wallet extension if it is compatible with the
latesttoolchain. If the extension is not yet compatible, use the latest release that is compatible. - The version of the Rust SDK should be the latest release that is compatible with the default version of
forc. - The version of the TypeScript SDK should be the latest release that is compatible with the default version of
forcand the Fuel wallet. - The version of the GraphQL API and Specs books should reflect the version of
fuel-coreused in the latest testnet. These books currently do not have regular releases.